本片博客是作者在学习c++的过程中的笔记记录,希望和各位读者一起学习交流
文章目录
类和对象
- 基本概念
类、对象、成员变量、成员函数
面向对象的三大概念:封装、继承、多态 - 类的封装
封装就是把数据(属性)和函数(操作)合成一个整体,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 - 类的访问控制关键字
- public:修饰的成员变量和函数,可以在类的内部和类的外部进行访问
- private:修饰的成员变量和函数,只能在类的内部被访问,不能在类的外部访问
- protected:修饰的成员变量和函数,只能在类的内部被访问,不能在类的外部访问,用在继承里面
- 在类中没有权限修饰的成员变量和函数,默认是private
- 类的前置声明
对象的构造和析构
构造和析构函数
- 构造函数和析构函数的概念
- 构造函数:
c++中的类可以定义与类名相同的特殊成员函数,这种与类名相同的成员函数就做构造函数
构造函数在定义时可以有参数
没有任何返回类型的声明 - 构造函数的调用:
自动调用、手动调用 - 析构函数:
c++中可以定义一个特殊的成员函数清理对象,这个特殊的成员函数叫做析构函数
语法:
~className()
析构函数没有参数也没有任何返回类型的声明
析构函数在对象销毁时自动被调用
c++编译器自动调用 - 在创建多个对象的时候,先创建的对象后释放
- 构造函数:
- 为什么需要构造函数和析构函数
初始状态是对象普遍存在地一种状态
如果没有构造函数,那么必须给每个类提供一个initialize函数进行属性的初始化,如果没有进行调用initialize,即对象就没有初始化,则属性的值是不确定的,同时会导致程序冗余。
构造函数的分类
- 无参构造函数
eg:test a; - 有参构造函数
用法:
编译器调用:test a(参数);
手动调用:test b = test(参数);这样会产生一个匿名对象 - 赋值构造函数(拷贝构造函数)(参数是对象自己):用一个对象初始化另一个对象
用法:
1. Test t1(1,2);
Test t0;
t0 = t1;//把t1赋值给t0 不会调用拷贝构造函数 会调用=操作符重载的函数
Test t3 = t1;//用t1初始化t3 会调用拷贝构造函数
赋值和初始化这两个操作是两个不同的概念
2. Test t4(t1);
3. 实参对象初始化形参对象会调用拷贝构造函数
4. 函数返回值为对象,但是a是一个局部变量,因此返回的是匿名对象,所以会调用匿名对象的拷贝构造函数
test g()//函数g会返回一个匿名对象
{
test a(1,2); //在这里会调用有参构造函数
return a; //在这儿会待用拷贝构造函数,用对象a初始化一个匿名对象
}
Test a = g();
//如果用匿名对象初始化另外一个同类型的对象,则g()返回的匿名对象会转换成一个有名字的对象,名字为a
Test a(1,2);
A = g();
//如果用匿名对象赋值给另一个同类型的对象,则g()返回的匿名对象会被析构掉 - 默认构造函数
注意: 1. 如果在类中没有显示的定义构造函数,则会默认地提供一个构造函数
2. 如果在类中没有提供一个拷贝构造函数,那么就会默认的提供一个拷贝构造函数
构造函数调用规则研究
- 当类中没有定义一个构造函数的时候,c++编译器会提供默认的无参构造函数和默认拷贝构造函数
- 当类中定义了构造函数的时候,c++编译器不会提供无参构造函数
- 默认拷贝构造函数是成员变量简单的赋值
深拷贝和浅拷贝
- 浅拷贝:对象之间属性的赋值,不涉及内存空间的操作
上面这一段程序的解读:
1. 创建t1对象,使用带参数的构造函数进行创建,在带参数的构造函数里面进行对象t1属性的初始化
2. 使用t1对象对t2对象进行初始化,会调用默认的拷贝构造函数,使t2的属性与t1的属性一样
3. 在最后main函数结束的时候,会进行内存空间的释放,进而导致会进行两次的内存释放,导致程序宕机 - 深拷贝:在类中对拷贝构造函数进行显示的定义,在实现的时候,进行内存的分配,因此要在上述的代码片中添加拷贝构造函数
当程序添加上拷贝构造函数的时候,用t1对t2进行初始化的时候就会显示的调用拷贝构造函数,进行内存的分配,进而不会导致内存泄漏,不会发生宕机
对象初始化列表
- 为什么要有对象初始化列表:
- 假设有一个类成员,它的本身是一个类或者是一个结构,同时它只有一个带参数的构造函数,没有默认的构造函数,这是要对这个类成员进行初始化就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,就将报错
下图是正确的示例:
- 当类成员中含有一个const对象或者是一个引用的时候,它们必须也通过初始化列表进行初始化,否则就会报错
下图是正确的示例:
总结:因为这两种对象要在声明之后要进行初始化,而在构造函数中,进行的是对他们的赋值。
- 假设有一个类成员,它的本身是一个类或者是一个结构,同时它只有一个带参数的构造函数,没有默认的构造函数,这是要对这个类成员进行初始化就必须调用这个类成员的带参数的构造函数,如果没有初始化列表,就将报错
- 语法:
Constructor::Constructor():m1(v1),m2(v1,v2),m3(v3)
{
//some other assignment operator
} - 注意概念:
初始化:被初始化的对象正在创建
赋值:被赋值的对象已经存在 - 注意:
成员变量的初始化顺序与声明的顺序相关,与在初始化列表中的顺序无关,初始化列表先于构造函数的函数体先执行
匿名对象的生命周期
eg: A(1); //这是定义一个匿名对象
这个对象的生命周期仅仅局限于这一句话,对象刚刚创建,就会被析构
对象的动态建立和释放
- new和delete基本用法
- new运算符:new运算符动态分配内存
- 使用形式:
指针变量 = new 类型(常量); //单个类型的对象
指针变量 = new 类型[表达式]; //对象数组 - 作用:从堆分配一块“类型”大小的存储空间,返回首地址
- 其中:“常量”是初始值,可缺省
创建数组对象时,不能为对象指定初始值
- 使用形式:
- delete运算符:delete运算符释放已分配的内存
- 使用形式:
delete 指针变量; //释放单个对象的内存
delete [] 指针变量; //释放对象数组内存 - 其中:“指针变量”必须是一个new返回的指针
- 注意:用new分配数组空间时不能指定初值,如果由于内存不足等原因而无法正常分配空间,则new会返回一个空指针NULL,用户可以根据该指针的值来判断分配空间是否成功。
- 使用形式:
- new运算符:new运算符动态分配内存
- malloc、free和new、delete的区别
malloc、free不会执行类的构造函数和析构函数
new、delete会执行类的构造函数和析构函数 - malloc、free可以和new、delete混合搭配使用
malloc分配的内存可以使用delete进行释放
new分配的内存可以使用free进行释放
下面的是笔者的微信公众号,欢迎关注,会持续更新c++、python、tensorflow、机器学习、深度学习等系列文章