点击链接加入群【C语言】:

    

上一小节主要讨论了引用,其实大家对指针的理解到位了,这些东西都很简单啦。

这小节,我们就来进入C++中的重要概念啦,类。还记得C中的结构体吗,可以用来描述事物,但是只能描述它们的属性,如Person结构,有名字,有年龄,但是它不能拥有方法,什么意思呢?比如人会吃饭,人会看书,人会浏览技术博客,但是结构定义的这个 Person 是不行的,但是我们也可以实现一种方式:

void Read(const Person * p,const char * bookname)

{

printf("%s is reading %s\n",p->name,bookname); //用指针访问成员用 -> 用对象访问成员用.

}

这样也可以实现某种功能,但是在C++中,我们对这种结构进行扩展,让它本身可以拥有方法(就是我们说的函数),这个就是C++里的类啦。

#include <cstdio>

class Persn

{

public:

intm_age;

char  *m_name;

public:

voidRead(const char * bookname)

{

printf("%s is reading %s\n",m_name,bookname);

}

};

int main()

{

Persn p;

p.m_age = 28;

p.m_name = "周星驰";

p.Read("大话西游");

return 0;

}

代码很简单,其实大家可以理解的,我们讲过代码只不过是 一些二进制代码的组合,放在哪里都是可以的,只要编译和链接过程中能够保证在调用的时候能找到它们就可以了。

所以我们现在的Person类有Read这个方法 ,很好理解吧,大家不用研究很深滴。这是一种思想的转变,从上面的Eat到现在的Eat的转变,大家能有所感触吧,或者你能体会到它们是不一样的过程就OK啦。下面是一种面向对象的思想,我们以后的工作都是在设计并实现一些类,这些类有很强大的功能。

现在大家要理解的一个东西是,代码就是二进制代码,写进.EXE中去啦,不管是以类的方式还是以什么的方式都可以。

下面我们来深入一下对类的了解。

其实就像我们定义一个 int val =12; 这样,也是有一个过程滴,就是在栈中申请内存,其实上面的Person p;也是在栈中申请内存啦,大家自己调试一下就会明白啦。

而我们写p.m_age 仅仅是为 p 对象的 前四个字节赋值,p.m_name 是对p对象的后四个字节赋值,那么我们调用p.Read就是调用为这个类生成的代码啦,可以想像一下,每一个类都可以有多种函数,可以再试想一下,这些代码肯定会有一个界限,就是A类的在A里面,B类的在B里面,这就是范围啦,对吧,其实这种思想很普遍,比如我们在main函数中定义的变量是在栈中存储的,在所有函数之外定义的变量是在数据区存储的,也叫全局变量,这就是一种范围上的划分吧,总之大家要理解好,我们在C++中称这种划分为作用域,你只要理解了思想,其实的什么都好说,既然有了作用范围,那么自然而然的我们也就会想到对象应该有生命周期,举个例子,在main函数中定义的变量,到main函数返回的时候就没有意义啦,也就是它的生命周期结束啦。

类跟普通的类型的区别就是它可以拥有方法,而且每个类还有两个特殊的方法,一个叫构造函数,一个叫析构函数。

构造函数的用处就是用来初始化类中的成员滴,如m_name,m_age, 析构函数就是用来销毁一些数据滴,这是编译器决定滴,也就当我们创造一个对象的时候,系统会自动调用它的构造函数,再对象的生命周期结束的时候,系统会自动调用它的析构函数。

很多书上会讲如果一个类没有定义构造函数,那么编译系统会为它添加一个默认的构造函数,虽然这不是什么重点,但是我们可以通过汇编代码看一下上面的例子,

00401036  rep stos  dword ptr [edi]

16:    Persn p;     //这里并没有调用默认构造函数

17:    p.m_age = 28;

00401038  mov     dword ptr [ebp-8],1Ch

18:    p.m_name = "周星驰";

0040103F  mov     dword ptr [ebp-4],offset string "\xd6\xdc\xd0\xc7\xb3\xdb" (00422028)

19:    p.Read("大话西游");

00401046  push    offset string "\xb4\xf3\xbb\xb0\xce\xf7\xd3\xce" (0042201c)

0040104B  lea     ecx,[ebp-8]

0040104E  call    @ILT+0(Persn::Read) (00401005)

好了,那么我们来为它加上一个构造函数吧。

#include <cstdio>

#include <cstdlib>

#include <cstring>

class Persn

{

public:

intm_age;

char  *m_name;

public:

Persn()

{

printf("构造函数被调用\n");

m_name = NULL;

m_name = (char *)malloc(20);

}

~Persn()

{

printf("析构函数被调用\n");

if (m_name != NULL)

{

free(m_name);

}

m_name = NULL;

}

voidRead(const char * bookname)

{

printf("%s is reading %s\n",m_name,bookname);

}

};

int main()

{

Persn p;

p.m_age = 28;

strcpy(p.m_name,"周星驰");

p.Read("大话西游");

return 0;

}

这个程序一运行,会看到先调用了构造函数,又调用了析构函数。如果你还不理解构造与析构,那么我再来总结一下,就是系统在构造这个对象之前,给我们一次机会,让我们对成员变量(如 m_age)做初始化,而析构函数也是在对象生命周期要结束的时候给我们一次机会,就是让我们做一些收尾工作(比如上面的free)。

在上面的代码中,我们有一个内容没有介绍,就是 #include,这里面写的是 cstdlib, cstdio  cstring,为什么这样写,在C++中,我们建议写这些,而不是stdio.h等,

也就是在C++中,我们要使用C语言中的一些库函数,头文件写成 (c stdio .h  ),将.H去掉,在前面加个字母c,很好理解吧。

现在大家应该知道,类有两个特殊函数,构造和析构,还知道了作用域和生命周期的概念。

那么我们还在代码中加上了 public 关键字,大家先就这样子用吧,以后再分析。

现在的问题是如果我们的类很大,写在同一个文件中很不好看,那么我们就要把它抽取出来。

Person.h

#include <cstdio>

#include <cstdlib>

#include <cstring>

class Persn

{

public:

intm_age;

char  *m_name;

public:

Persn();

~Persn();

voidRead(const char * bookname);

};

Person.cpp

#include "Person.h"

Persn::Persn()

{

printf("构造函数被调用\n");

m_name = NULL;

m_name = (char *)malloc(20);

}

Persn::~Persn()

{

printf("析构函数被调用\n");

if (m_name != NULL)

{

free(m_name);

}

m_name = NULL;

}

voidPersn::Read(const char * bookname)

{

printf("%s is reading %s\n",m_name,bookname);

}

Test.cpp

#include "Person.h"

int main()

{

Persn p;

p.m_age = 28;

strcpy(p.m_name,"周星驰");

p.Read("大话西游");

return 0;

}

大家只要把这三个文件创建出来,将代码打出来就行了,然后再从全局的角度来看这段代码

我们在Person.cpp里写Person类的函数实现的时候,加上了 Person::  大家能猜到这是什么吗?作用域标识符,哈哈,就是说这个函数是 Person类特有的,那么比如,我们要调用一些全局函数怎么办,就用全局作用域标识符,怎么写 ::,就这么简单,举个例子比较好理解哦。

#include <windows.h>

class CFindWindow

{

public:

HWNDm_Window;

CFindWindow():m_Window(NULL)

{

//这里我们使用的是参数列表的形式进行初始化,多个参数之间用逗号隔开

}

voidFindWindow(const char * className = NULL,const char * windowName = NULL)

{

HWND temHwnd = ::FindWindow(className,windowName);

if (temHwnd != NULL)

{

m_Window = temHwnd;

}

}

};

int main()

{

CFindWindowobj;

obj.FindWindow("Notepad");

return 0;

}

这段代码中我们写了一个用来查找系统中是不是存在某个窗口,我们在运行这段代码的时候应该先按 WIN 键 + R ,WIN键不知道是哪个?就是键盘上的窗口键,按下这个组合键后会弹出运行对话框,在里面输入notepad 后回车,会打开一个记事本窗口,然后我们在obj.FindWindow那句下断点,调试程序,会发现没调用函数之前,obj.m_Window的值是0,调用后,它就有值了,我们在这里封装的这个在里面提供了一个FindWindow的方法,WINDOWS系统提供的用来查找窗口的API函数也是这个名字,怎么办呢,我们要调用API,所以就加上 :: 全局作用域标识符,来告诉系统,我们调用的是系统提供的全局函数,而不是我们这个类的这个FindWindow,不然的话,就成什么呀,递归嘛。。不过好像没有出口吧,而且我们讲过没出口的递归是不应该出现滴,以后再看MFC中的很多函数,都是对API的一种封装,就像我们现在这样类似。

还介绍了一个初始化列表,就是在 构造函数的()后面 加上 :成员变量1(初始值),成员变量2(初始值),成员变量3(初始值){ } , 当然这个操作也可以只对部分成员做初始化。

好啦,我们已经讲的很多啦。

再一介绍一下 public的意思,这个意思嘛,就是说明,我们在main函数中可以访问的意思,如 obj.FindWindow ,这个操作是在main函数中进行滴,如果改成 private或protect 会导致编译出错,那么它们又是什么意思呢。

private 说明这个成员是这个类私有的,只允许这个类的成员函数进行访问,如

class A

{

private:

int m_data;

public:

void fun(){ //在这里面可以访问m_data}  //但是在main函数中定义A obj. obj.m_data是不行滴。理解了吧,所以说数据一般私有化,我们一般提供一个公有函数来对它进行访问

}

protect:表示这个成员可以被派生类访问,以后再介绍。

其实我们在真正的项目中,对应用程序的数据控制一般不会很严格,一般就是 数据私有化,方法公有化。

下一小节,要给大家介绍一下C++ 的简单模板技术。 很给力吧。