> 文章列表 > c++学习(day3)

c++学习(day3)

c++学习(day3)

文章目录

  • 一. C++中的动态内存分配和回收
    • 1.1 内存的分配
    • 1.2 内存的回收
    • 1.3 new\\delete与malloc\\free的区别(笔试面试)
  • 二. C++中的函数
    • 2.1 函数重载(overload)
      • 2.1.1 概念
      • 2.1.2 要求
      • 2.1.3 调用
    • 2.2 函数的默认参数
    • 2.3 函数重载和默认参数同时出现
    • 2.4 哑元(了解)
    • 2.5 内联函数(inline)
      • 2.5.1 内联函数和带参宏的区别
  • 三. C++中的结构体
  • 四. 类
    • 4.1 c++中的类
    • 4.2 类的定义格式
    • 4.3 封装
  • 五. **c++中类的结构体的区别**
    • 5.1 区别内容
    • 5.2 使用场景
  • 六. this指针
    • 6.1 this指针的内涵
    • 6.2 this指针的格式
    • 6.3 必须使用this指针的场景
  • 七. 类中特殊的成员函数
    • 7.1 构造函数
      • 7.1.1 功能
      • 7.1.2 格式
      • 7.1.3 调用时机
      • 7.1.4 构造函数支持重载
      • 7.1.5 构造函数初始化列表
      • 7.1.6 必须使用初始化列表的情况
    • 7.2 析构函数
      • 7.2.1 功能
      • 7.2.2 格式
      • 7.2.3 调用时机
      • 7.2.4 默认析构函数
      • 7.2.5 构造函数和析构函数调用顺序
    • 7.3 拷贝构造函数
      • 7.3.1 功能
      • 7.3.2 格式
      • 7.3.3 调用时机
      • 7.3.4 深拷贝和浅拷贝
      • 谈一谈深浅拷贝问题(笔试面试题)
    • 7.4 拷贝赋值函数
      • 7.4.1 功能
      • 7.4.2 格式
      • 7.4.3 调用时机
      • 7.4.4 也涉及深浅拷贝问题
      • 7.4.5 空类中会默认提供哪些函数
  • 八. 匿名对象
  • 九. 类的大小
      • 7.4.4 也涉及深浅拷贝问题
      • 7.4.5 空类中会默认提供哪些函数
  • 八. 匿名对象
  • 九. 类的大小

一. C++中的动态内存分配和回收

对于堆区空间的开辟和回收,C语言使用malloc、free函数进行操作。C++中有专门进行堆区空间使用的关键字new、delete

new、delete申请空间时,以数据类型作为单位进行申请,所以会分单个内存的分配和回收,以及连续内存的分配和回收

1.1 内存的分配

  1. 单个内存的申请:数据类型 *指针名 = new 数据类型;

    语言 方式
    C++ int *p = new int;
    C语言 int *p = (int *)malloc(sizeof(int))
  2. 连续内存的申请:数据类型 *指针名 = new 数据类型[个数];

    语言 方式
    C++ int *p = new int[5];
    C int *p = (int *)malloc(sizeof(int) * 5)

1.2 内存的回收

  1. 单个内存的释放:delete 指针名;

    语言 方式
    C++ delete p
    C free§
  2. 连续内存的释放:delet []指针名;

    语言 方式
    C++ delete []p 释放所有空间
    C free§
#include <iostream>using namespace std;int main()
{// 单个内存的分配int *p1 = new int;               // 在堆区申请一个int大小的空间,将内存地址给p1cout << "*p1 = " << *p1 << endl; // 随机值*p1 = 520;cout << "*p1 = " << *p1 << endl; // 520// 单个内存分配后并初始化int *p2 = new int(1314);         // 在堆区申请一个int类型的空间,将1314放进去,将地址赋值给p2cout << "*p2 = " << *p2 << endl; // 1314// 单个内存空间的释放delete p1;p1 = nullptr; // NULLdelete p2;p2 = nullptr;// 连续内存分配和回收int *p3 = new int[5]; // 在堆区连续申请5个int大小的空间,将地址赋值给p3// 给内存空间赋值for (int i = 0; i < 5; i++){p3[i] = i + 10;}// 输出数据for (int i = 0; i < 5; i++){cout << p3[i] << " ";}cout << endl;// 连续内存申请后初始化int *p4 = new int[5]{10, 20, 30, 40, 50};// 输出数据for (int i = 0; i < 5; i++){cout << p4[i] << " ";}cout << endl;// 释放内存空间delete[] p3;delete[] p4;p3 = nullptr;p4 = nullptr;return 0;
}

1.3 new\\delete与malloc\\free的区别(笔试面试)

  1. malloc\\free属于函数调用,而new和delete属于关键字

  2. malloc申请空间时不能初始化,而new申请空间时可以初始化

  3. malloc申请空间时以字节为单位,而new申请空间时以数据类型为单位

  4. malloc返回的结果为void *,而new返回的结果依据申请的数据类型而定

  5. malloc、free申请和释放空间时无需考虑单个还是连续内存,而new、delete需要考虑

  6. malloc申请空间时要是有sizeof计算申请空间的大小,而new不需要

  7. new申请空间时会自动调用构造函数,而malloc不会(后期讲)

  8. delete释放空间时会调用析构函数,而free不会(后期讲)

二. C++中的函数

2.1 函数重载(overload)

2.1.1 概念

属于静态多态的一种,能够实现“一名多用”

背景:有时程序员定义函数,可能会因为函数参数的类型不同,导致相同功能的函数,要定义多个版本,调用起来比较麻烦

C++提供了函数重载机制,也就是允许定义多个函数同名

2.1.2 要求

  1. 函数名必须相同

  2. 函数参数必须不同(类型不同、个数不同),跟返回值无关

  3. 作用域必须一致

2.1.3 调用

函数调用时,系统会根据实参的类型,自动匹配相应的函数来完成调用

#include <iostream>using namespace std;int my_sum(int m, int n) // 定义函数求两个整数的和   my_sumii
{return m + n;
}int my_sum(int m, int n, int k) // 定义函数求三个整数的和   my_sumiii
{return m + n + k;
}float my_sum(float m, float n) // 定义函数求两个小数的和  my_sumff
{return m + n;
}string my_sum(string m, string n) // 定义函数求两个字符串的和 my_sum。。。。。。。
{return m + n;
}int main()
{cout << my_sum(3, 8) << endl; // 11cout << my_sum(3, 8, 5) << endl; // 15cout << my_sum((float)3.5, (float)8.2) << endl; // 11.7cout << my_sum("hello", "world") << endl; // helloworldreturn 0;
}

2.2 函数的默认参数

  1. C语言中函数的形参的值必须由函数实参进行传递,不能设置成默认参数

  2. C++提供了函数默认参数机制:定义函数时给某个形参或某几个形参设置初始值(默认值),那么,这些参数,如果主调函数传递了数据,就使用主调函数传的数据,如果主调函数不传递数据,则使用自己的默认值

  3. 由于函数实参传递原则“靠左原则”,那么函数形参默认参数设置原则为“靠右原则”,只有右边的形参全部设置成默认参数后,左边的形参才能设置默认参数

  4. 当主调函数写在被调函数之前,则需要对被调函数进行声明,那么函数的默认参数要在函数声明部分进行设置,函数定义部分无需进行设置

#include <iostream>using namespace std;int my_sum(int m, int n = 100, int k = 100); // 函数声明int main()
{cout << my_sum(3, 8, 5) << endl; // 16cout << my_sum(3, 8) << endl;    // 111    如果传递的参数和定义的参数不一致,则使用默认值cout << my_sum(3) << endl;       // 203return 0;
}int my_sum(int m, int n, int k) // 定义函数求三个整数的和   my_sumiii
{return m + n + k;
}

2.3 函数重载和默认参数同时出现

需要注意自定义的重载函数,与带默认参数的函数是否存在功能覆盖问题,如果有重复,则定义时不报错,调用时会产生混乱

#include <iostream>using namespace std;int my_sum(int m, int n = 100, int k = 100)
{return m + n + k;
}// 定义一个参数的函数
int my_sum(int m)
{return m;
}int main()
{cout << my_sum(3, 8, 5) << endl; // 16cout << my_sum(3, 8) << endl; // 111// cout << my_sum(3) << endl;             //203   定义函数不会报错,但是调用时会混乱return 0;
}

2.4 哑元(了解)

所谓哑元,就是在定义函数时,某个形参或者某几个形参,只有参数类型,在函数体内没有使用,仅仅是起到占位作用,没有实际意义

场景一:在一个程序中,需要大量调用某个函数,该函数有3个参数。经过运营一年后,发现,需要对该函数进行升级,将原本需要三个参数升级为只需要两个参数,但是,此时该函数在程序中已经被调用成千上万次了,如果要对函数参数进行改变,则函数调用部分全部都进行修改,作业量较大而且不现实。此时我们就可将其中某个参数设置成哑元,只起到占位作用,函数体内不使用该变量。

使用场景二:在自增自减运算符重载中,用以区分前置自增自减和后置自增自减(后期讲)

#include <iostream>using namespace std;int my_sum(int m, int, int k) // 此时第二个参数为哑元,只起占位作用
{return m + k;
}int main()
{cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;return 0;
}

2.5 内联函数(inline)

  1. 内联函数会建议编译器在编译阶段将该函数在主调函数调用处展开

  2. 内联函数定义格式:只需在定义函数前加关键字:inline即可

  3. 内联函数的要求:

    • 要求函数体较小

    • 要求函数调用频繁

优点:提供代码运行效率

缺点:容易造成主调函数代码膨胀

#include <iostream>using namespace std;// 定义内联函数
inline int my_sum(int m, int, int k) // 此时第二个参数为哑元,只起占位作用
{return m + k;
}int main()
{cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;cout << my_sum(1, 2, 3) << endl;return 0;
}

2.5.1 内联函数和带参宏的区别

#include <iostream>using namespace std;// 定义内联函数	
inline int my_max(int m, int n) // 此时第二个参数为哑元,只起占位作用
{return m > n ? m : n;
}// 定义带参宏
#define MAX(m, n) m > n ? m : n			//只做替换不做计算int main()
{int a = 1;int b = 1;int c;c = MAX(++a, b);                                               // ++a > b ? ++a : b;cout << "a = " << a << "  b = " << b << "  c = " << c << endl; // 3  1  3a = 1;b = 1;c = my_max(++a, b);                                            // 2 > 1 ? 2:1;cout << "a = " << a << "  b = " << b << "  c = " << c << endl; // 2  1  2return 0;
}

三. C++中的结构体

c和c++结构体的区别:

  1. C++中的结构体允许封装函数,但是C语言中不允许

  2. C++中的结构体能对成员属性进行初始化,而C语言中不允许

  3. C++中结构体定义变量时,可以不用加struct,直接使用结构体名定义即可,而C语言不允许

  4. C++中的结构体有访问权限控制,而C语言中的结构体没有

  5. C++中的结构体允许被继承,而C语言中的结构体不允许

  6. C++中的结构体中允许封装另一个结构体类型,而C语言中一般只能在体内使用

struct 结构体名
{public:	该访问权限为公共权限,结构体中、子结构体中、结构体外都能被访问private: 该权限下的所有属性属于私有属性,结构体中可以使用,结构体外不能使用protected: 该访问权限下的成员能在结构体中、子结构体中被访问,外部不能访问还可以直接定义函数}

示例:

#include <iostream>
using namespace std;struct Person
{
public:                     // 该访问权限为公共权限,结构体中、子结构体中、结构体外都能被访问string name = "zhangs"; // C++中结构体允许给属性加初始值int age = 18;// C++的结构体中允许封装函数void show(){cout << "name = " << name << "  age = " << age << "  sex = " << sex << endl;}// 结构体中封装一个结构体struct Car{string color; // 颜色int wheel;    // 车轮};protected:            // 该访问权限下的成员能在结构体中、子结构体中被访问,外部不能访问int money = 1000; // 私房钱private: // 该权限下的所有属性属于私有属性,结构体中可以使用,结构体外不能使用char sex = 'F';
};// 定义学生 结构体继承自人结构体
struct Stu : Person
{void display(); // 结构体内声明private:double score = 99;
};// 结构体外定义
void Stu::display()
{cout << "name = " << name << endl;cout << "  age = " << age << endl;// cout<<"  sex = "<<sex<<endl;      //Person的私有成员,虽然继承下来了,但是不能使用cout << " money = " << money << endl; // Person的受保护成员cout << "score = " << score << endl;  // 自己的私有成员,结构体中能被访问
}int main()
{struct Person p1;cout << "p1.name = " << p1.name << endl; // zhangs// p1.sex ;            //私有访问权限下的属性,不能在结构体外被访问Person p2;                             // C++中定义结构体变量不需要加structcout << "p2.age = " << p2.age << endl; // 18p2.name = "zhangpp";p2.show();// 使用学生结构体定义变量Stu s1;s1.show();// s1.score;        //Stu的私有成员,只能在Stu中使用,外界不可用// s1.sex;           //Person的私有成员,只能在Person中使用,外界不可用// s1.money;       //Person中的受保护成员,可以在Person、Stu中使用,外界不可用// 结构体中还可以封装一个结构体类型Person::Car c;c.color = "red";c.wheel = 3;return 0;
}

四. 类

主要完成属性(成员变量)、方法(成员函数)的封装

面向对象的三大特征:封装、继承、多态

4.1 c++中的类

C++中的类是由C++中的结构体演化而来的,只是默认访问权限、默认继承方式不同而已

4.2 类的定义格式

class  类名
{public://公共的属性、方法protected://受保护的属性、方法private://私有的属性、方法
};

注意:

  1. 类中没有一个访问权限,则默认为私有访问权限
  2. public:公共权限,该权限下的成员可以在类内、子类中、类外被访问
  3. protected:受保护权限,该权限下的成员可以在类内、子类中被访问,类外不能被访问
  4. private:私有权限,该权限下的成员只能在类内被访问,子类、类外不允许访问

示例:

#include <iostream>using namespace std;class Stu
{
public:string name; // 公共权限下的成员protected:int score; // 受保护的权限下的成员private:int age; // 私有权限下的成员public:void show(); // 类内声明
};// 类外定义
void Stu::show()
{cout << "name = " << name << endl;   // 公共权限下,类内允许访问cout << "score = " << score << endl; // 受保护权限下,类内允许被访问cout << "age = " << age << endl;     // 私有权限下,类内允许被访问
}int main()
{Stu s1;              // 使用类定义变量,这个过程也叫实例化对象的过程s1.name = "zhangpp"; // 类中公共成员,类外允许被访问// s1.score = 99;          //类中受保护成员,类外不允许访问// s1.age = 18;            //类中私有成员,类外不允许被访问return 0;
}

4.3 封装

  1. 将能够实现某一事物的所有的属性(变量)、和行为(函数)都封装到一个整体,我们称这个整体为类。在类中会提供公共的接口,用户可以使用公共的接口对该类实例化的对象进行操作。

  2. 类中会给成员提供不同的访问权限

关键字 类内权限 子类权限 类外权限
public(公有属性) V V V
protected(受保护的属性) V V X
private(私有属性) V X X
  1. 类中的访问权限是针对于类体而言,跟对象无关

  2. 类中的成员函数,可以访问类中各种权限下的成员

  3. 一个访问权限在类体中可以出现多次,但是一般为了好看,我们将同一权限下的成员放到一起

  4. 每个访问权限作用域为从该关键字开始到下一个关键字出现或者类体结束为止

  5. 一般将成员属性设为私有权限,成员方法设为公共权限

  6. 如果类中没有给定访问权限,默认访问权限为private

五. c++中类的结构体的区别

5.1 区别内容

  1. 默认访问权限不同,结构体的默认访问权限是公共的,类的默认访问权限是私有的

  2. 默认继承方式不同,结构体的默认继承方式是public,类的默认继承方式是私有继承

5.2 使用场景

  1. 结构体一般用于数据结构相关的内容,如定义结点

  2. 一般定义构造数据类型时,使用类完成

六. this指针

在类体内系统自动给非静态成员函数提供的特殊的指针

6.1 this指针的内涵

是指代对象本身,哪个对象使用我,我就指向哪个对象

6.2 this指针的格式

类名 * const this; //可以通过this改变内容,但是不能更改this指针的指向

6.3 必须使用this指针的场景

  1. 当普通成员函数的形参名和成员变量名同名时,必须使用this指针加以区分

    当构造函数的形参名和成员变量名同名时,在函数体内,使用this指针区分,在初始化列表中不需使用this也能解决

  2. 在拷贝赋值函数中,要返回自身引用时,必须使用this指针(后期讲)

#include <iostream>using namespace std;class Rectangle
{
private:int width;  // 宽int length; // 长public:void init(int width, int length){this->width = width;this->length = length;// this = nullptr;      //this指向不能被更改}// 展示函数void show(){cout << "width = " << this->width << "  length = " << this->length << endl;cout << "this : " << this << endl;}
};int main()
{Rectangle r;r.init(4, 5);r.show(); // 4,5cout << "&r = " << &r << endl;Rectangle r1;r1.show();cout << "&r1 = " << &r1 << endl;return 0;
}

七. 类中特殊的成员函数

一个类中有六个特殊成员函数:构造函数、析构函数、拷贝构造、拷贝赋值、取地址重载、常取地址重载函数,

C++11后新增两个:移动构造、移动赋值函数,其中前四个最常用

之所以说特殊,是因为这些函数如果程序员不手动定义,系统会默认提供,如果手动定义了,系统就不会提供默认的了

这些函数,不需要显性调用,在特殊时机,系统会自动调用

7.1 构造函数

7.1.1 功能

当使用类去实例化对象时,用于给类对象申请空间和初始化使用的

7.1.2 格式

  1. 没有返回值

  2. 函数名与类名同名

  3. 访问权限:一般为公共的

  4. 参数可以有可以没有

  5. 格式

    类名(形参列表)
    {函数体内容
    }
    

7.1.3 调用时机

使用类实例化对象时,系统会自动调用

  1. 在栈区: 实例化对象,何时实例化对象,系统何时调用构造函数

    类名 对象名; //无参构造

    类名 对象名(参数); //有参构造

  2. ==在堆区:==定义指针时,不调用构造函数,只有使用new实例化空间时才调用构造函数

    类名 *指针名; //不调用构造函数

    指针名 = new 类名; //此时调用构造函数

#include <iostream>using namespace std;class Stu
{
private:string name;int age;public:Stu(){cout << "Stu::无参构造" << endl;}Stu(string n, int a){this->name = n;this->age = a;cout << "Stu::有参构造" << endl;}
};int main()
{Stu s1; // 在栈区,实例化对象时系统自动调用构造函数Stu s2("zhangpp", 18); // 调用有参构造Stu *p;                    // 此时不会调用构造函数p = new Stu("zhangs", 20); // 此时会调用有参构造return 0;
}

7.1.4 构造函数支持重载

  1. 当类中没有显性给定构造函数,系统会默认提供一个无参构造,来完成对类对象的空间申请以及初始化工作

  2. 当类中显性定义了构造函数,系统就不会再提供那个无参构造了,如果在类体内需要用到无参构造,则需要手动定义出无参构造

  3. 一个类中可以定义多个构造函数,对不同的数据进行初始化,这些函数构成重载关系

#include <iostream>using namespace std;class Stu
{
private:string name;int age;public:Stu() // 无参构造{cout << "Stu::无参构造" << endl;}Stu(string n, int a) // 有参构造{this->name = n;this->age = a;cout << "Stu::有参构造" << endl;}Stu(string n) // 有参构造{name = n;}Stu(int a) // 有参构造{age = a;}// 以上四个构造函数构成重载关系
};int main()
{Stu s1;                // 在栈区,实例化对象时系统自动调用构造函数Stu s2("zhangpp", 18); // 调用有参构造Stu s3("lisi");        // 合法Stu s4(99);            // 合法Stu s5{"libai", 66};   // 调用构造函数Stu *p;                    // 此时不会调用构造函数p = new Stu("zhangs", 20); // 此时会调用有参构造return 0;
}

7.1.5 构造函数初始化列表

  1. C++提供构造函数初始化列表机制,如果在构造函数的函数体内执行赋值语句,那么此时便不是初始化

  2. 在构造函数的形参列表后,由冒号引出初始化列表,完成构造函数的初始化工作

  3. 格式:类名(形参列表):成员变量1(形参1),成员变量2(形参2)… 成员变量n(形参n){ 函数体内容 }

#include <iostream>using namespace std;class Rectangle
{
private:int width;  // 宽int length; // 长public:Rectangle(int width, int length) : width(width), length(length) // 使用初始化列表完成成员的初始化工作{// 赋值,不是初始化//        this->width = width;//        this->length = length;// this = nullptr;      //this指向不能被更改cout << "rectangle::有参构造" << endl;}// 展示函数void show(){cout << "width = " << this->width << "  length = " << this->length << endl;cout << "this : " << this << endl;}
};int main()
{Rectangle r(4, 5);r.show();return 0;
}

7.1.6 必须使用初始化列表的情况

  1. 当构造函数的形参名和成员变量名同名时,可以使用初始化列表解决这一冲突

  2. 当类中有引用成员时,对该成员必须在构造函数的初始化列表中对其进行初始化

  3. 当类中有const修饰的成员变量时,对该成员也必须使用初始化列表来完成初始化工作

  4. 当类中有其他类的成员子对象时,对该成员也必须使用初始化列表来完成初始化工作,如果没有显性调用有参构造,系统会默认调用无参构造

// 类中有引用成员的案例
#include <iostream>
using namespace std;class Rectangle
{
private:int width;   // 宽int &length; // 长public:Rectangle(int width, int &length) : width(width), length(length) // 使用初始化列表完成成员的初始化工作{// 赋值,不是初始化//        this->width = width;//        this->length = length;// this = nullptr;      //this指向不能被更改cout << "rectangle::有参构造" << endl;}// 展示函数void show(){cout << "width = " << this->width << "  length = " << this->length << endl;cout << "this : " << this << endl;}
};int main()
{int l;Rectangle r(4, l);r.show();return 0;
}/类中有其他类的成员子对象案例/
#include <iostream>using namespace std;class Teacher
{
private:string sub; // 科目
public:Teacher() { cout << "Teacher::无参构造" << endl; }explicit Teacher(string s) : sub(s) { cout << "Teacher::有参构造" << endl; }
};class Stu
{
private:string name;int age;Teacher t; // 其他类的子对象public:Stu() : t(" "){cout << "Stu::无参构造" << endl;}Stu(string n, int a, string s) : t(s) // 此时调用的t的有参构造来对其进行初始化{this->name = n;this->age = a;// this->t = s;              //隐式调用cout << "Stu::有参构造" << endl;}Stu(string n){name = n;}Stu(int a){age = a;}// 以上四个构造函数构成重载关系
};int main()
{Stu s1("zhangpp", 18, "C++");return 0;
}

7.2 析构函数

7.2.1 功能

在对象消亡时回收对象的内存空间以及善后工作的

7.2.2 格式

  1. 没有返回值

  2. 函数名是在类名前加~

  3. 一般为公共权限下

  4. 没有参数

  5. 格式

    ~类名()
    {函数体内容;
    };
    

7.2.3 调用时机

栈区:对象的声明周期结束后,系统自动调用析构函数

堆区:何时使用delete何时调用析构函数

#include <iostream>
using namespace std;class Stu
{
private:string name;public:// 定义无参构造函数Stu() { cout << "Stu::无参构造" << endl; }// 定义有参构造函数Stu(string n) : name(n){cout << "Stu::有参构造" << endl;}// 析构函数~Stu(){cout << "Stu::析构函数" << endl;cout << "this = " << this << endl;}
};int main()
{Stu *p = new Stu; // 堆区申请空间cout << "p = " << p << endl;delete p; // 手动调用delete完成对堆区空间的析构Stu s("zhangpp"); // 栈区空间cout << "&s = " << &s << endl;return 0;
}

7.2.4 默认析构函数

  1. 如果类中没有显性定义析构函数,系统会默认提供一个析构函数来完成类对象消亡时对象内存的回收

  2. 如果类中显性定义了析构函数,系统就不再提供默认的析构函数了

  3. 如果类中无指针成员,则使用系统提供的析构函数没有问题,但是,如果类中有指针成员,则需要显性定义析构函数,在析构函数的函数体内,将指针空间的内存进行释放。

  4. 一个类中只能有一个析构函数,没有重载版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GOVGoXgJ-1682225097333)(C:/Users/XiongJiwei/AppData/Roaming/Typora/typora-user-images/image-20230420214436509.png)]

7.2.5 构造函数和析构函数调用顺序

  1. 在堆区:何时使用new何时调用构造函数,何时使用delete何时调用析构函数

  2. 在栈区:先构造的对象后析构,后构造的对象先析构

#include <iostream>using namespace std;class Stu
{
private:string name;int *age;public:// 定义无参构造函数Stu() : age(new int(0)) { cout << "Stu::无参构造" << endl; }// 定义有参构造函数Stu(string n, int a) : name(n), age(new int(a)){cout << "Stu::有参构造" << endl;}// 析构函数~Stu(){delete age;cout << "Stu::析构函数" << endl;cout << "this = " << this << endl;}
};int main()
{Stu s1("zhangpp", 18); // 栈区空间cout << "&s1 = " << &s1 << endl;Stu s2("wangwu", 20); // 栈区空间cout << "&s2 = " << &s2 << endl;// 在栈区,先构造的对象后析构,后构造的对象先析构return 0;
}

7.3 拷贝构造函数

7.3.1 功能

是特殊的构造函数,用来完成一个对象给另一个对象初始化时,系统自动调用

例如:string s1 = “hello”; //有参构造

string s2 = s1; string s3(s1); stirng s4{s1}; //调用拷贝构造

7.3.2 格式

  1. 没有返回值

  2. 函数名与类同名

  3. 参数:同类的其他类对象的引用

  4. 权限:public

  5. 格式

    类名 (类名 & other) { 函数体内容; }

7.3.3 调用时机

  1. 使用一个类对象给另一个类对象初始化时,系统自动调用

  2. 当类对象作为函数参数传递时,系统自动调用拷贝构造函数

  3. 当函数的返回值是类对象时,系统也会自动调用拷贝构造函数

#include <iostream>using namespace std;class Stu
{
private:string name;int age;public:// 定义无参构造函数Stu() : age(0) { cout << "Stu::无参构造" << endl; }// 定义有参构造函数Stu(string n, int a) : name(n), age(a){cout << "Stu::有参构造" << endl;}// 析构函数~Stu(){cout << "Stu::析构函数" << endl;// cout<<"this = "<<this<<endl;}// 定义拷贝构造函数Stu(Stu &other) : name(other.name), age(other.age){cout << "Stu::拷贝构造" << endl;}void show(){cout << "name = " << name << "   age = " << age << endl;}
};Stu fun(Stu s) // 当函数参数是类对象时,实参传递时调用拷贝构造函数// 当函数返回值是类对象时,也会调用拷贝构造函数
{return s;
}int main()
{Stu s1("zhangpp", 18); // 有参构造// 在栈区,先构造的对象后析构,后构造的对象先析构Stu s3 = s1; // 拷贝构造        //如果没有显性定义拷贝构造函数,系统会默认提供一个拷贝构造函数// 来完成类对象成员之间的简单赋值fun(s1);return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g60zizQ6-1682225097334)(C:/Users/XiongJiwei/AppData/Roaming/Typora/typora-user-images/image-20230422135932772.png)]

7.3.4 深拷贝和浅拷贝

  1. 如果在类中没有显性定义拷贝构造函数,系统会默认提供一个拷贝构造函数,来完成两个类对象成员之间的简单赋值

  2. 如果显性定义了拷贝构造函数,系统就不再提供默认的拷贝构造函数了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fZNooQZG-1682225097334)(C:/Users/XiongJiwei/AppData/Roaming/Typora/typora-user-images/image-20230422140021869.png)]

#include <iostream>using namespace std;class Stu
{
public:string name;int age;double *score;public:// 定义无参构造函数Stu() : age(0) { cout << "Stu::无参构造" << endl; }// 定义有参构造函数Stu(string n, int a, double d) : name(n), age(a), score(new double(d)){cout << "Stu::有参构造" << endl;}// 析构函数~Stu(){delete score; // 释放指针成员cout << "Stu::析构函数" << endl;// cout<<"this = "<<this<<endl;}// 定义拷贝构造函数// Stu(Stu &other):name(other.name), age(other.age),score(other.score)      //浅拷贝Stu(Stu &other) : name(other.name), age(other.age), score(new double(*(other.score))) // 深拷贝{cout << "Stu::拷贝构造" << endl;}void show(){cout << "name = " << name << "   age = " << age << endl;}
};int main()
{Stu s1("zhangpp", 18, 99); // 有参构造Stu s3 = s1; // 拷贝构造cout << "s1.score = " << s1.score << endl;cout << "s3.score = " << s3.score << endl;return 0;
}

谈一谈深浅拷贝问题(笔试面试题)

  1. 如果没有显性定义拷贝构造函数,系统会默认提供一个拷贝构造函数,这个构造函数是一个浅拷贝,只能完成成员变量之间的简单赋值。当类中没有指针成员时,使用浅拷贝没有问题。

  2. 当类中有指针成员时,使用浅拷贝会造成两个类对象的指针成员指向同一块内存空间,在析构时会出现同一段内存空间释放两次(double free)的情况,造成程序执行出问题。此时需要显性定义拷贝构造函数来完成深拷贝。

  3. 在拷贝构造函数的初始化列表中,给指针成员单独申请空间,然后将原对象的指针指向内存空间中的值,赋值到新空间中,即可完成深拷贝

7.4 拷贝赋值函数

7.4.1 功能

使用一个类对象给另一个类对象赋值时,系统自动调用。其本质是运算符重载。

7.4.2 格式

  1. 返回值:左操作数自身的引用

  2. 函数名:operator=

  3. 参数:同类的其他类对象

  4. 格式

    类名 &(const 类名 &other) { }

#include <iostream>using namespace std;class Stu
{
public:string name;int age;double *score;public:// 定义无参构造函数Stu() : age(0), score(new double(0)) { cout << "Stu::无参构造" << endl; }// 定义有参构造函数Stu(string n, int a, double d) : name(n), age(a), score(new double(d)){cout << "Stu::有参构造" << endl;}// 析构函数~Stu(){delete score; // 释放指针成员cout << "Stu::析构函数" << endl;// cout<<"this = "<<this<<endl;}// 定义拷贝构造函数// Stu(Stu &other):name(other.name), age(other.age),score(other.score)      //浅拷贝Stu(Stu &other) : name(other.name), age(other.age), score(new double(*(other.score))) // 深拷贝{cout << "Stu::拷贝构造" << endl;}// 定义拷贝赋值函数,完成深拷贝Stu &operator=(const Stu &other){if (this != &other){this->name = other.name;this->age = other.age;// this->score = other.score;          //浅拷贝*(this->score) = *(other.score); // 深拷贝}cout << "拷贝赋值函数" << endl;return *this; // 返回自身的引用}// 定义移动赋值函数Stu &operator=(const Stu &&other){cout << "移动赋值" << endl;}void show(){cout << "name = " << name << "   age = " << age << endl;}
};int main()
{Stu s1("zhangpp", 18, 99); // 有参构造Stu s3 = s1; // 拷贝构造cout << "s1.score = " << s1.score << endl;cout << "s3.score = " << s3.score << endl;Stu s2; // 无参构造s2 = s1; // 此时会调用拷贝赋值函数  s2.operator=(s1)// s2 = move (s1);       //移动赋值return 0;
}

7.4.3 调用时机

使用一个类对象给另一个类对象赋值时,系统自动调用

例如:string s1(“hello world”); //有参构造

string s2; //无参构造

s2 = s1; //拷贝赋值函数

7.4.4 也涉及深浅拷贝问题

  1. 如果没有显性定义拷贝赋值函数,系统会默认提供一个拷贝赋值函数,这个拷贝赋值函数是一个浅拷贝,只能完成成员变量之间的简单赋值。当类中没有指针成员时,使用浅拷贝没有问题。

  2. 当类中有指针成员时,使用浅拷贝会造成两个类对象的指针成员指向同一块内存空间,并且自己的内存空间泄漏,在析构时会出现同一段内存空间释放两次(double free)的情况,造成程序执行出问题。此时需要显性定义拷贝赋值函数来完成深拷贝。

  3. 在拷贝赋值函数的函数体内,将要赋值的对象指针空间中的值,赋值到自己对象指针空间中,即可完成深拷贝

7.4.5 空类中会默认提供哪些函数

class Test
{//无参构造Test(){}//析构函数~Test(){}//拷贝构造Test(const Test &other){}//拷贝赋值Test &operator=(const Test &other){}
};

八. 匿名对象

  1. 匿名对象生命周期较短

  2. 匿名对象由类名直接调用构造函数得到

#include <iostream>using namespace std;class Stu
{
private:string name;public:Stu() { cout << "无参构造" << endl; }Stu(string n) : name(n) { cout << "有参构造" << endl; }Stu(const Stu &other) : name(other.name){cout << "拷贝构造" << endl;}void show(){cout << "name = " << name << endl;}
};// 全局函数
void fun(Stu s)
{s.show();
}int main()
{Stu s = Stu("zhangpp"); // 该对象就是匿名对象   匿名对象使用方式1s.show();// 匿名对象使用方式2:给对象数组初始化Stu k[3] = {Stu("zhangsan"), Stu("lisi"), Stu("wangwu")};for (int i = 0; i < 3; i++){k[i].show();}fun(Stu("zhanglong")); // 匿名对象使用方式3:当做函数形参传递return 0;
}

九. 类的大小

  1. 类的大小本质上是结构体的大小,要遵循结构体字节对 方式

  2. 一个空类默认给定1字节的大小,用于占位作用

  3. 成员函数不占类的内存大小,即使函数中定义其他变量,也不占类的内存

        //拷贝赋值函数
    

7.4.4 也涉及深浅拷贝问题

  1. 如果没有显性定义拷贝赋值函数,系统会默认提供一个拷贝赋值函数,这个拷贝赋值函数是一个浅拷贝,只能完成成员变量之间的简单赋值。当类中没有指针成员时,使用浅拷贝没有问题。

  2. 当类中有指针成员时,使用浅拷贝会造成两个类对象的指针成员指向同一块内存空间,并且自己的内存空间泄漏,在析构时会出现同一段内存空间释放两次(double free)的情况,造成程序执行出问题。此时需要显性定义拷贝赋值函数来完成深拷贝。

  3. 在拷贝赋值函数的函数体内,将要赋值的对象指针空间中的值,赋值到自己对象指针空间中,即可完成深拷贝

7.4.5 空类中会默认提供哪些函数

class Test
{//无参构造Test(){}//析构函数~Test(){}//拷贝构造Test(const Test &other){}//拷贝赋值Test &operator=(const Test &other){}
};

八. 匿名对象

  1. 匿名对象生命周期较短

  2. 匿名对象由类名直接调用构造函数得到

#include <iostream>using namespace std;class Stu
{
private:string name;public:Stu() { cout << "无参构造" << endl; }Stu(string n) : name(n) { cout << "有参构造" << endl; }Stu(const Stu &other) : name(other.name){cout << "拷贝构造" << endl;}void show(){cout << "name = " << name << endl;}
};// 全局函数
void fun(Stu s)
{s.show();
}int main()
{Stu s = Stu("zhangpp"); // 该对象就是匿名对象   匿名对象使用方式1s.show();// 匿名对象使用方式2:给对象数组初始化Stu k[3] = {Stu("zhangsan"), Stu("lisi"), Stu("wangwu")};for (int i = 0; i < 3; i++){k[i].show();}fun(Stu("zhanglong")); // 匿名对象使用方式3:当做函数形参传递return 0;
}

九. 类的大小

  1. 类的大小本质上是结构体的大小,要遵循结构体字节对 方式

  2. 一个空类默认给定1字节的大小,用于占位作用

  3. 成员函数不占类的内存大小,即使函数中定义其他变量,也不占类的内存

  4. 当类中有虚函数时,会多加一个虚指针的空间(后期讲)