一.目录
1.对象的相关知识
2.类的定义
3.类的实例化
4.类对象模型
5.模拟实现offsetof宏
6.this指针
二.正文
1.对象的相关知识
C语言是面向过程的,关注的是过程,分析求解问题的步骤,通过函数调用逐步解决问题。
C++是面向对象的,关注的是对象,将一件事拆分成不同的对象,靠对象之间的交互完成。
对象:任何一个对象都应该具有两个要素,即属性和行为,对象是由一组属性和行为构成的。如现实生活中的手机就是一个对象,它的属性就是生产厂家,配置,颜色等等,行为就是它的功能。在一个系统中的多个对象之间通过一定的渠道相互联系,如下图:
在C++中,每个对象都是由数据和函数(即操作代码)这两部分构成的,数据体现了属性,函数就是对数据进行操作的,以便实现某些功能。(通过三角形这个对象的边长,通过数学公式(函数)计算出三角形的面积(实现功能)。
2.类的定义
C语言中,结构体中只能定义变量,在C++中,结构体中不仅可以定义变量,还可以定义函数。
C++中的类就是将对象的属性和行为结合在一块,通过访问权限将接口提供给外部用户使用,也就是类的封装属性,用户不需要知道具体的实现方法,进而提高了数据的安全性。
类的访问限定符:
public(公有):修饰的成员变量在类外也可以被直接访问
protected(保护):在类外不能被直接访问
private(私有):在类外不能被直接访问
类访问限定符的作用域从该访问限定符出现的位置到下一个访问限定符出现时为止
注意:在C++中,为了兼容C语言的一些特性,struct的功能和C语言相同,但增加了新的功能,即可以在struct中定义函数,struct和class相同,struct默认成员为public,private不加访问限定符时默认成员为private。
访问限定符只在编译期间有作用,当数据 映射到内存的时候没有任何访问限定符上的区别
类的具体实现:
(1)关键字:class
(2)用法:class [classname],{}为类的主体,注意类定义结束后的封号
1 class Student 2 { 3 void TestFun(){//成员函数 4 } 5 int a;//成员变量 6 };
类的两种定义方法:
(1)声明和定义都放在类体中,注意:如果成员函数在类体中,则编译器在编译期间会将类的成员函数当做内联函数展开(不会在Debug版本下展开,而在发布版本中展开)
1 class Student 2 { 3 public: 4 int Add(int math, int english){ 5 score=math+english; 6 cout<<a<<" "<<name<<" "<<endl; 7 } 8 public: 9 int score; 10 char *name; 11 };
(2)在类中申明(放在.h文件中),在类外定义(放在.c文件中)
//Student.h文件 class Student { public: void Add(int math, int english); void PrintScore(); private: int score; }; //Student.c void Student::Add(int math, int english) { score=math + english; } void Student::PrintScore() { cout<<score<<" "<<endl; }
注意:这种方法在定义时需要用作用域限定符“::”指定定义的函数属于这个类作用域
3.类的实例化
定义:用类类型创建对象的过程称之为类的实例化
解释:一个类可以建立多个对象,定义类的时候操作系统不会为其开辟内存空间,只有当用类类型定义了对象之后对象才会占用物理地址空间,存储类成员变量。打个比方,类就像现实生活中建筑物的建筑图纸,而建筑物就是用图纸建的对象,图纸中的的建筑物并不真实存在,但建造(实例化)后的建筑物真实存在于客观世界。
1 class Student//类 2 { 3 public: 4 int Add(int math, int english){ 5 score=math+english; 6 } 7 public: 8 char *name; 9 int score; 10 int id; 11 }; 12 13 int main() 14 { 15 Student S1;//对象 16 S1.Add(89,94); 17 cout<<score<<" "<<endl; 18 return 0; 19 }
4.类对象模型
(1)如何计算类对象的大小?
由于类对象具有成员函数和成员变量,那么该如何计算类的大小?这就是下面要讲的。
(2)类对象的存储方式猜测
a.如果成员函数和成员变量存储在连续内存空间中,则当一个类的成员变量较多时,而且定义多个对象时,会将里面的成员变量与成员函数都复制一份为其开辟内存,每个对象都会保存一份代码,但是这些对象都共用同一个函数,相同代码保存多次,浪费空间,因此不会为每个对象都复制一份代码。
b.还有另一种可能情况,如下图所示情况,只保存成员变量,成员函数放在公共的代码段
c.为了验证上面两种猜测,我们可以通过代码求得类对象的大小
1 class Student 2 { 3 pupblic: 4 void PrintScore(){ 5 cout<<score<<" "<<endl; 6 } 7 int Add(int math , int english) 8 { 9 score=math + english; 10 } 11 private: 12 int score; 13 } 14 15 int main() 16 { 17 Student S1; 18 cout<<sizeof(S1)<<" "<<endl; 19 reutrn 0; 20 }
通过上述代码可以求得类对象的大小,进而判断出第二种猜测是正确的。即成员函数在公共代码区,不会为每个对象都保存一份公共的代码。类的对象的大小求解方法和C语言中的结构体的大小相同。存在内存对齐的方式。
一个类的大小,实际上就是该类成员变量所占内存之和,当然也要进行内存对齐,注意空类的大小,空类比较特殊,编译器给空类一个字节来唯一标识这个类。
(3)结构体中的内存对齐规则
a.第一个成员变量在与结构体偏移量为0的地址处
b.其他成员要对齐到某个数字(对齐数)的整数倍地址处。注意:对齐数=编译器默认的对齐数与该成员变量的较小值(VS中的默认对齐数是8,gcc中的默认编译器是4
c.结构体的总大小为:最大对齐数(所有变量的类型最大值与编译器默认对齐数取最小)的整数倍处
d.如果嵌套了结构体,嵌套的结构体对齐到自己最大的整数倍处,结构体的整体的大小就是所有最大对齐数的整数倍(含有嵌套结构体的对齐数)
e.可以用#pragma peak(n)这个预处理指令设置默认的对齐数为n字节对齐,当然,n应该是2的k次方,不然无法对齐。
f.通过offsetof这个宏可以求解得某个成员相对于结构体的起始位置的偏移量offsetof(类名,成员名)
5.模拟实现offsetof宏
1 #define offsetof(TYPE,MEMBER) (size_t)&((TYPE *)0)->MEMBER)
这个宏可以根据下图理解:
6.this指针
(1)在看this指针前先看一个例子:
1 class Student 2 { 3 public: 4 void PrintFun(){ 5 cout<<_gender<<" "<<endl; 6 cout<<_name<<" "<<endl; 7 cout<<_age<<" "<<endl; 8 } 9 void Setstudentinfo(char *name, char* gender, int age) 10 { 11 strcpy(_name,name); 12 strcpy(_gender,gender); 13 _age=age; 14 } 15 private: 16 char *_name; 17 char* _gender; 18 int _age; 19 }; 20 21 int main() 22 { 23 Student S1; 24 Student S2; 25 S1.Setstudentinfo("TOM","male",18); 26 S2.Setstudentinfo("perter","male",20); 27 S1.PrintFun(); 28 S2.PrintFun(); 29 return 0; 30 }
对于上述两个类,有这样一个问题:
Student类中有Setstudentinfo和PrintFun两个成员函数,函数体中没有关于不同对象的区分,而且这两个成员函数存储 在代码公共区,那么当S1调用Setstudentinfo函数时,该函数如何知道应该设置S1对象而不是设置S2对象呢?
C++通过引入this指针解决了这个问题,即:C++编译器给每个“成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中的所有成员变量的操作,都是通过this指针去访问。只不过所有的操作对于用户是透明的,即用户不用传递该参数,编译器自动完成。
(2)this指针的特性
a.this指针的类型:类类型 * const this
b.只能在“成员函数”内部使用
c.this指针本质上其实是一个成员函数的第一个参数,是对象调用成员函数时,将对象的地址传给this指针,所以对象中不存储this指针。
d.this指针是成员函数的第一个隐藏的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
注意:this指针不能作为左值,只能在“成员函数”内部使用。并不是所有的函数的this指针都用ecx寄存器传递,比如不定参数函数的this指针就用eax寄存器函数压栈的方式传递地址。这是因为调用函数时的调用约定不同,有的函数前面加了_thiscall(通过ecx寄存器传递),而有的函数前面加了_cdecl(通过压栈的方式传递)
this指针可以为空值,但如果是空值,则在成员函数内部不能访问对象的成员变量。如下面的代码:
1 class A 2 { 3 public: 4 int Add(int left , int right){ 5 a=left+right; 6 } 7 void PrintA(){ 8 cout<<a<<endl; 9 } 10 private: 11 int a; 12 } 13 14 int main() 15 { 16 A *p=NULL;//定义一个类类型的指针,指向这个A类 17 p->Add(1,2);//此时类类型的指针是空值,调用它的成员函数的时候讲NULL复制给this指 针,this指针为NULL,则执行Add函数时因this访问a,所以会报错。 18 p->PrintfA(); 19 return 0; 20 }