类和对象
C++面向对象的三大特性:封装、继承、多态。 (与java的特性一致)
封装
struct与class的区别
默认的访问权限不同
struct 默认权限为公共
class 默认权限为私有
成员属性设置为私有
优点:
将所有的成员属性
对于写权限,我们可以检测数据的有效性
对象的初始化和清理
生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设。
构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题
一个对象或者变是没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数决上述问题,这两个函数将会被编译器自动调角,完成对象初始化和清理工作,对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编泽器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class person {public : person (){ std::cout << "无参构造函数-- " << std::endl; } ~person (){ std::cout << "析构函数-- " << std::endl; } }; int main () { person pe; }
构造函数的分类及调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class person {public : person () { std::cout << "无参构造函数-- " << std::endl; } person (int a) { age = a; std::cout << "有参构造函数-- " << std::endl; } person (const person &p) { age = p.age; std::cout << "拷贝构造函数-- " << std::endl; } ~person () { std::cout << "析构函数-- " << std::endl; } private : int age; }; void main{ person p () ; person p1; person p2 (10 ) ; person p3 (p2) ; person p1; person p2 = person (10 ); person p3 = person (p2); person (10 ); person (p3); person p4 = 10 ; }
拷贝构造函数调用的时机
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 person p2 (10 ) ;person p3 (p2) ;>>>>>>>>>>>>>> void func (person p) {} person p; func (p);>>>>>>>>>>>>>> person func () { person p1; return p1; } void func2 () { person p = func (); } int main () { func2 (); }
构造函数调用规则
如果我们写了有参构造函数,编译器不再提供默认构造函数,依然提供拷贝构造
如果我们写了拷贝构造函数,编译器就不再提供其他普通构造函数
深拷贝与浅拷贝
浅烤贝:简单的赋值拷贝操作
深烤贝:在堆区重新申请空间,进行拷贝操作
存在的问题解析:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 class person {public : person () { std::cout << "无参构造函数-- " << std::endl; } person (int a, int height) { age = a; m_height = new int (height); std::cout << "有参构造函数-- " << std::endl; } ~person () { if (m_height != NULL ){ delete m_height; m_height =NULL ; } std::cout << "析构函数-- " << std::endl; } int age; int *m_height; }; void func2 () { person p1 (18 ,178 ) ; std::cout << "p1 = " << p1.age << std::endl; std::cout << "p1-2 = " << *p1.m_height << std::endl; person p2 (p1) ; std::cout << "p2 = " << p2.age << std::endl; std::cout << "p2-2 = " << *p2.m_height << std::endl; } int main () { func2 (); }
错误原因分析:
在执行person p1(18,178);
时,p1在栈内存中开辟的一个空间,存放的是age的值,m_height = new int(height);
操作是在堆空间开辟了一个空间,并且栈空间的m_height
的值就是 new int(height)
在堆空间开辟的内存的地址。所以p1开辟的空间存放的是:age=值,m_height=堆空间的地址。
person p2(p1);
此操作就发生了浅拷贝,就是把p1字段的值拷贝给p2字段的值,所以p2与p1内存所存储的数据一致。在对象销毁前会走析构函数,所以p2对象销毁的时候会走一次析构函数,在析构函数里会走 delete m_height;
释放掉m_height所占的内存,接着在p1对象销毁前也会走一次析构函数,再次去释放了m_height所占的内存,此时就会报错了。
如果避免此错误的发生,就是重写拷贝函数,在拷贝函数里做深拷贝。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class person {public : person () { std::cout << "无参构造函数-- " << std::endl; } person (int a, int height) { age = a; m_height = new int (height); std::cout << "有参构造函数-- " << std::endl; } person (const person &p){ age = p.age; m_height = new int (*p.m_height); } ~person () { if (m_height != NULL ){ delete m_height; m_height =NULL ; } std::cout << "析构函数-- " << std::endl; } int age; int *m_height; };
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
初始化列表
作用:用来初始化属性
语法:构造函数():属性1(值1),属性2(值2),属性3(值3),……
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 struct Student { Student (string name,int age,float score):name (name),age (age),score (score){ } string name; int age; float score; }; int main () { Student st ("张三" , 19 , 68.5 ) ; std::cout << " name = " << st.age << std::endl; std::cout << " age = " << st.name << std::endl; std::cout << " score = " << st.score << std::endl; }
类对象作为类成员
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class Soldier {public : Soldier (string sName){ m_sName = sName; } string m_sName; }; class General {public : General (string gName,string sName):m_gName (gName),soldier (sName){ } string m_gName; Soldier soldier; }; void ewsrew () { General g1 ("李云龙" ,"和尚" ) ; std::cout << g1.m_gName << "的属下是" << g1.soldier.m_sName << std::endl; }
当其他类作为本类的成员,构造的时候会先构造其他类对象,再构造自身对象。调用析构函数时是先调用自身的析构函数,再调用其他类成员的析构函数
静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称之为静态成员
静态成员分为:
静态成员变量
所有对象共享一份数据
在编译阶段分配内存(全局区)
类内声明,类外初始化
静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
静态成员函数有两种访问方式:
通过对象进行访问
通过类名进行访问
(静态成员函数也是如此)
C++对象模型和this指针
成员变量和成员函数分开存储
在C++中,类内和成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
空对象占用内存空间为:1
C++ 编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置,每个空对象也应该有一个独一无二的内存地址