01 构造函数和构造函数初始化列表
在C/C++中,变量(对象)在定义的时候,可以被初始化,如果不初始化,变量的值是什么呢?
未初始化的全局变量和静态变量 ------> 0 存储在数据段(.bss),在程序开始时,统一刷新为0
未初始化的局部变量 -----> 随机值(不可预测的) 栈
int a = 100; // 初始化 int b; b = 100; // 不是初始化,是赋值
对于C++中的对象(自定义类型的变量)来说,对象和基本变量一样,定义的时候也可以进行初始化,如果不初始化,状态就是未知的(代码中,任何一个对象都应该有一个确定的状态)
但是,一个对象的内部结构可能比较复杂,在定义的时候,如果不进行初始化,在使用的时候就可能会产生一些错误
如:
有一些以指针为成员变量的类在实例化的时候,如果没有初始化,成员指针就是野指针class Test { private: char *m_name; public: void setName(const char *str) { strcpy(name, tsr); // 此处就会访问野指针 } }
我们的屏幕对象,在实例化之后,必须经过"初始化"工作,确定内部的状态
....
所以,对象的初始化往往不仅仅是对成员变量的赋值那么简单,也可能还需要进行一些动态内存分配,打开文件等复杂的操作,这种情况下,我们就不能以初始化基本类型的方式来对对象进行初始化了....
虽然可以为类设计一个"初始化"函数,对象在实例化之后,就立即调用这个函数如:
Screen s; // 实例化一个屏幕对象
s.init(); // 初始化屏幕
但是这样做的话,也有一个缺点,初始化函数就不具有强制性,很难确保程序每一次实例化之后都去调用它
而且面向对象的程序设计语言倾向于对象(变量)在实例化之后,一定要有一个确定的状态(值),使用起来才会比较安全
因此,C++引入了构造函数(constructor)的概念,用于对对象进行自动初始化工作
1. 构造函数(constructor)
构造函数:在创建一个新对象的时候一定会调用
功能:专门用于对象的初始化工作,在类的对象创建时定义初始状态特点:
1. 构造函数的名字和类名相同 !!!2. 构造函数没有返回值类型,也不能写void,可以有参数(可以重载,可以有很多个构造函数),参数可以设置默认值,不能产生二义性
3. 在创建对象的时候,自动调用,而且一定会调用,且只调用一次
不能通过已有对象手动调用构造函数 !!!4. 如果一个类中没有显示的声明构造函数,编译器会自动的生成一个构造函数
自动生成的构造函数函数体为空且没有参数
类名() {}
如果你自己显示的写了任何构造函数时,编译器就不会自动生成了 !!!
5. 不要因为构造函数的名称而认为构造函数负责为对象分配空间,构造函数在执行的时候,对象的内存空间就已经分配好了,构造函数的作用只是给成员变量赋值,初始化资源
#include <iostream> using namespace std; class Test { private: int m_a = 1; // C++11标准之后可以这样写 // 如果构造函数有重新给它赋值,那么就会修改这个值 int m_b = 2; int *m_p; public: // 构造函数(自己写了构造函数,编译器就不会自动生成了) // 可以有参数(可以重载,可以有很多个构造函数),参数可以设置默认值 // 不能产生二义性 Test(int a, int b = 2) { cout << "有参-构造函数" << endl; m_a = a; m_b = b; m_p = (int *)malloc(sizeof(int)); // malloc的返回值为void * *m_p = 1024; } // 重载的构造函数 Test() { cout << "无参-构造函数" << endl; m_a = 100; m_b = 100; m_p = (int *)malloc(sizeof(int)); *m_p = 1024; } void show() { cout << "m_a:" << m_a << endl; cout << "m_b:" << m_b << endl; cout << "*m_p:" << *m_p << endl; } }; int main() { // Test *pt; // 不会调用构造函数,没有创建对象,仅仅是一个指针类型 // 编译器在调用构造函数的时候,会根据构造函数参数自动匹配相应的构造函数 // 在创建对象的时候,没有提供实际参数,编译器会自动查找 Test::Test() Test t; // 调用无参构造函数,实例化一个 Test 对象 t.show(); // t.Test(10, 11); // 对象一旦创建,就不能手动调用构造函数 // 在创建对象的时候,同时指定初始化参数(调用有参构造函数) // 类名 对象名(实际参数列表); Test t1(12, 13); // 调用有参构造函数(Test::Test(int, int)) // 实例化一个 Test 对象 t1.show(); // Test t2(); // 这不是实例化对象,而是声明了一个函数 // 函数返回值为Test,函数名为t2,没有参数 // t2.show(); // 为了解决这个问题,C++11新标准中引入了一种新的初始化方式 // 就是在初始化的时候统一使用大括号 // 如: Test t3{}; // 调用无参构造函数 Test t4{100}; // 调用Test::Test(int) Test t5{100, 101}; // 调用Test::Test(int, int) // Test t6{100, 101, 102}; // 调用Test::Test(int, int, int) // ---> 没写这个构造函数,找不到会报错 return 0; } /* 无参-构造函数 m_a:100 m_b:100 *m_p:1024 有参-构造函数 m_a:12 m_b:13 *m_p:1024 无参-构造函数 有参-构造函数 有参-构造函数 */
注意两个概念:
无参构造函数:没有任何参数的构造函数
默认构造函数:不提供参数就可以调用的构造函数,可以是参数有默认值
默认构造函数包含了无参构造函数
C++中如何清理需要销毁的对象呢?
一般而言,需要销毁的对象都需要进行清理工作(释放资源)
如:关闭文件,释放动态内存分配的空间.....
解决方案:
为每一个类提供一个公有的 free函数
对象不需要的时候,立即调用 free函数 释放资源class Test { private: int *m_p; public: Test() { m_p = malloc(4) }; void free() { ::free(m_p); } }; 存在一个问题, free只是一个普通的函数,必须要显示的调用 对象在销毁前没有调用这个函数,很有可能会造成资源浪费 ======> C++编译器在对象销毁的时候(超出作用域)能够自动调用一个特殊的函数进行对象的清理工作
2. 析构函数(de-structor)
C++编译器在对象销毁的时候能够自动调用一个特殊的函数进行对象的清理工作
=====> 这个特殊的清理函数就是析构函数
析构函数的作用和构造函数的作用是相反的 =====> 释放对象占用的资源
定义:~类名() {}
功能:
专门用于对象的清理工作
特点:1. 析构函数的名字和类名相似 ~类名
2. 析构函数没有返回值类型,也不能写void,也没有参数 (不能设置默认值,不能重载,只能有一个析构函数)
3. 在对象销毁的时候自动调用(不提倡使用类对象手动调用)
4. 如果一个类中没有显示的声明析构函数,编译器会自动的生成一个析构函数
自动生成的析构函数函数体为空
问题:1. 在相同的作用域下面,多个对象的构造和析构顺序是怎么样的?
按照声明的顺序构造对象
先构造的对象,后析构,后构造的对象,先析构2. 全局对象和静态对象在什么时候构造,什么时候析构?
全局对象(包括全局静态对象)在程序开始前构造,在程序结束之后析构
局部静态对象在用到的时候构造,在程序结束之后析构#include <iostream> using namespace std; class Test { private: int _a; public: Test(int a) { _a = a; cout << "有参构造" << " " << _a << endl; } ~Test() { cout << "无参构造" << " " << _a << endl; } }; static Test t1{1}; Test t2{2}; static Test t3{3}; int main() { cout << "===========" << endl; Test t4{4}; static Test t5{5}; { Test t6{6}; } } /* 有参构造 1 有参构造 2 有参构造 3 =========== 有参构造 4 有参构造 5 有参构造 6 无参构造 6 无参构造 4 无参构造 5 无参构造 3 无参构造 2 无参构造 1 */
3. 构造函数和析构函数是做什么的,如何使用的?
构造函数:初始化对象
析构函数:释放资源
能不能不写构造函数和析构函数:
能
在什么情况下必须手动的添加构造函数和析构函数?
a. 对象在创建出来时需要有初始化的时候
b. 对象中拥有资源(指针,打开文件,....)的时候一定要自己实现构造函数和析构函数
为前面写的代码添加构造函数和析构函数
只有屏幕类需要实现构造函数和析构函数(只有屏幕类拥有资源)
arm-linux-g++ *.cpp -o main -std=c++11
screen.hpp
#ifndef __SCREEN_HPP__ #define __SCREEN_HPP__ #include "size.hpp" #include "rect.hpp" // 头文件不能相互包含,相互保护就会递归展开 #include "color.hpp" #include "point.hpp" #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <iostream> #include <errno.h> #include <sys/mman.h> #include <unistd.h> using namespace std; class Rect; // 前向声明,防止头文件递归展开 // 屏幕类的描述 class Screen { private: // 屏幕的尺寸 Size m_size; char m_name[32]; // 路径名 int m_fd; int *m_plcd; public: // 初始化(构造函数) Screen(const char *name = "/dev/fb0", int w = 800, int h = 480); // 关闭(析构函数) ~Screen(); // 清屏 void clearScreen(int color = 0x00ffffff); void clearScreen(Color c); // 画点 void drawPoint(int x, int y, int color); void drawPoint(Point p, Color c); // 屏幕有一个行为是显示矩形 void drawRect(Rect r); }; #endif
screen.cpp
#include "screen.hpp" // 初始化(构造函数) Screen::Screen(const char *name, int w, int h) { m_size.setSize(w, h); strcpy(m_name, name); m_fd = open(m_name, O_RDWR); if (-1 == m_fd) { perror("open lcd failed"); exit(0); } m_plcd = (int *)mmap(NULL, w * h * 4, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0); if (m_plcd == MAP_FAILED) { perror("mmap lcd failed"); ::close(m_fd); // 调用无名空间中的close exit(0); } } // 关闭(析构函数) Screen::~Screen() { // 解映射 munmap(m_plcd, m_size.getW() * m_size.getH() * 4); // 关闭 ::close(m_fd); // 调用无名空间中的close } // 清屏 void Screen::clearScreen(int color) { // 把所有的点显示为指定的颜色 int w = m_size.getW(); int h = m_size.getH(); for (int y = 0; y < h; y++) { // 遍历每一行 for (int x = 0; x < w; x++) { // 遍历每一列 drawPoint(x, y, color); } } } void Screen::clearScreen(Color c) { int color = c.getColor(); clearScreen(color); } // 画点 void Screen::drawPoint(int x, int y, int color) { if (x < 0 || x >= m_size.getW() || y < 0 || y >= m_size.getH()) { // cout << "point out of the Screen!" << endl; return; } *(m_plcd + m_size.getW() * y + x) = color; } void Screen::drawPoint(Point p, Color c) { drawPoint(p.getX(), p.getY(), c.getColor()); } // 屏幕有一个行为是显示矩形 void Screen::drawRect(Rect r) { // 把矩形形容的所有的点都显示为矩形中的颜色 int x0 = r.getPoint().getX(); // 矩形的起点横坐标 int y0 = r.getPoint().getY(); int w = r.getSize().getW(); int h = r.getSize().getH(); int c; r.getColor(&c); for (int y = y0; y < y0 + h; y++) { for (int x = x0; x < x0 + w; x++) { drawPoint(x, y, c); } } }
4. 当A(Rect)类对象中拥有一个B(Point)类对象时,创建和销毁A类对象时,A和B的构造函数是如何调用的? C++中,主张任何对象的初始化工作由自己的构造函数完成
调用A类的构造函数前,先调用B类的构造函数(先把成员对象构造出来)析构的时候,先调用A类的析构函数,再调用B的析构函数
构造一个对象时,成员对象先被构造(先构造的后析构)
如果A类中拥有多个其他类的对象,其他类的构造函数的调用顺序是按照在A中声明顺序调用的
如果B类对象只有有参构造函数的时候,怎么办?
默认调用的是B类对象的无参构造函数,如果B类对象没有无参构造函数,就需要想办法!!!======>
通过构造函数初始化列表的方式指定B类对象的构造函数
如果在一个对象(A)中拥有另一个类的对象(B)时,在构造这个对象(A)的时候,会先默认调用成员对象(B)的无参构造函数,以初始化成员对象B
当类的成员对象(B)中,没有无参构造函数时,就会因为找不到对应的构造函数而报错
任何解决:
1. 手动在成员对象类中写一个无参构造函数
2. 在成员对象类的有参构造函数中,给参数添加默认值
1和2这两种方法都是让编译器可以以没有参数的形式调用成员对象的无参构造函数
3. 利用构造函数初始化列表的方式指定成员对象的构造函数 <------
通常是使用第三种方法,因为前面两种方法都需要修改成员对象类,有可能成员对象类是不可以修改的
3. 构造函数初始化列表
时间线:类类型——构造函数初始化列表——对象实例化创建——构造函数
构造函数初始化列表是对构造函数的一种增强,它能够初始化一些特殊成员如:
常量,引用,调用成员对象指定的构造函数......
在构造函数中,仅仅是对成员变量的赋值,这里严格意义上来说,不是初始化
仅仅是一个赋值计算的过程,当成员变量是常量的时候,常量是不能赋值的,给常量赋值就会报错
格式:构造函数名(参数列表) : 成员变量名1{初始值}, 成员变量名2{初始值}...... {
构造函数的函数体!!!
}
如果成员变量名是另一个类(B)的对象(b),那么这种情况下就是指定成员对象(b)的初始化方式(构造函数)
当成员变量名和参数列表里面的变量名相同的时候
()里面一定是参数列表里面的变量名
()外面的一定是成员变量名
a(a):外面的a就是成员a,里面的a就是参数a
()里面是可以给表达式的
必须使用构造函数初始化列表的场合:1. 成员变量是常量(const)
2. 成员变量是引用(在定义的时候必须初始化)
3. 成员对象没有无参构造函数,指定成员对象特定的构造函数(指定成员对象的初始化方法)
4. 初始化基类成员(指定基类的构造函数)
建议:
成员变量的初始化尽量都使用构造函数初始化列表的方式
1. 代码简洁
2. 初始化列表的这种方式效率比普通构造函数高
注意:
构造函数的声明和定义分开的时候,构造函数初始化列表必须写在定义的地方!!!
/* t.m_x:随机值 t.m_y:101 在构造函数初始化列表中,初始化成员的顺序是按照类中的声明顺序来的 而不是按照出现在初始化列表中的顺序来的 在上面,虽然m_y出现在前面,但是m_x先初始化 */ #include <iostream> using namespace std; class Test { private: int m_x; int m_y; public: Test(int x, int y):m_y(y), m_x(m_y) {} void show() { cout << m_x << endl; cout << m_y << endl; } }; int main() { Test t{100, 101}; t.show(); return 0; }
02 临时对象(无名对象)
临时对象是指在函数传参或者函数返回的时候,创建的没有名字的对象
无名对象在用完之后,会被立即销毁,用户也不能去使用它
临时对象只能做右值,不能做左值
例子: Color c; // 实例化一个颜色对象,表示一个颜色 c.setColor(0xff, 0xff, 0xff); Screen s; // 实例化一个屏幕类对象 s.clearScreen(c); // 把屏幕对象设置为c表示的颜色 // 此处的c对象仅仅是用来作为函数的参数传递的,其他的地方并没有用到 ====================> Screen s; // 实例化一个屏幕类对象 s.clearScreen(Color{}); // 调用Color的无参构造函数 // 构造一个临时对象,作为参数传递进入函数内部 // 临时对象的生存期也仅仅在这一行 or Screen s; // 实例化一个屏幕类对象 s.clearScreen(Color{0xff, 0xff, 0xff}); // 调用Color的有参构造函数 // 构造一个临时对象,作为参数传递进入函数内部
Color{} or Color{0xff, 0xff, 0xff} 都会创建一个临时对象,只不过这个对象我们用户没有办法去引用它,用完之后会立即被销毁
例子:
#include <iostream> using namespace std; class Test { private: int m_x; int m_y; public: Test(int x, int y):m_x(x), m_y(y) { cout << "构造函数" << endl; } Test(const Test& t):m_x(t.m_x), m_y(t.m_y) { cout << "拷贝构造函数" << endl; } ~Test() { cout << "析构函数" << endl; } void show() { cout << "m_x:" << m_x << endl; cout << "m_y:" << m_y << endl; } }; /* Test t(形式参数) = t(实际参数); 函数调用时把实际参数赋值给形式参数 形式参数t时实际参数t的一个副本 调用了"拷贝构造函数"(利用一个已有对象生成一个新对象) */ void fun(Test t) { t.show(); // Test{10, 11}; // 此处仅仅是创建一个临时对象,和t对象没有关系 // t.show(); } Test fun2() { // Test t{20, 21}; // return t; return Test{20, 21}; // 把临时对象作为一个返回值 // 编译器会优化,直接构造到返回值对象中 } int main() { Test t{100, 101}; t.show(); // Test{}; // 调用无参构造函数创建临时对象 cout << "---------" << endl; Test{100, 101}; cout << "---------" << endl; fun(t); /* Test t = Test{12, 13}; 先创建一个无名对象,再利用这个无名对象生成一个形参t对象 但是编译器会产生优化,会直接把名字对象构造到它要赋值的对象中去 可以关闭编译器的这种优化,编译的时候在后面加上: -fno-elide-constructors fun(Test{12, 13}); // 生成一个临时变量,作为函数的参数 */ // fun(Test(12, 13)); // 生成一个临时对象作为函数的参数 Test rt = fun2(); // 使用函数的返回值对象去初始化rt对象 // 会产生编译器优化 /* 理解: 因为作用域的原因,会创建一个第三方对象,两者都可以访问的对象 拷贝构造函数 所以会有一次构造,两次拷贝构造,三次析构 */ rt.show(); cout << "---------" << endl; return 0; } // g++ 1.cpp -std=c++11 /* 构造函数 m_x:100 m_y:101 --------- 构造函数 析构函数 --------- 拷贝构造函数 m_x:100 m_y:101 析构函数 构造函数 m_x:20 m_y:21 --------- 析构函数 析构函数 */ // g++ 1.cpp -std=c++11 -fno-elide-constructors /* 构造函数 m_x:100 m_y:101 --------- 构造函数 析构函数 --------- 拷贝构造函数 m_x:100 m_y:101 析构函数 构造函数 拷贝构造函数 析构函数 拷贝构造函数 析构函数 m_x:20 m_y:21 ------- 析构函数 析构函数 */
✔03 对象的实例化
实例化:根据已有的类型创建对象的过程
可以在栈中实例化对象(局部变量)
可以在堆中实例化对象(动态变量)
可以创建全局 / 静态对象
全局对象定义在大括号外面的对象,就是全局对象
作用域是全局的
主函数开始前构造,程序结束后析构
静态对象:全局静态对象 生存期是随进程的持续性,作用域仅在本文件有效
局部静态对象 生存期是随进程的持续性,作用域是局部的用法和C语言中的static修饰变量是一样的
在栈中创建对象(局部对象)
形式和定义普通变量一致
数据类型 变量名;
======>
类名 对象名;
类名 对象名{}; // 调用默认构造函数,实例化一个普通对象
or
类名 对象名{参数 ... }; // 调用有参构造函数,实例化一个普通对象
定义一个对象数组(一组对象)
数组 ---> 元素类型 数组名[元素个数];
======>
类名 对象名[元素个数]; // 调用默认构造函数
类名 对象名[元素个数]{实际参数}; // 调用有参构造函数
// Test ta[10]; // 实例化一个对象数组,调用10次默认构造函数
Test tb[10] = {{1, 2}, {3, 4}, {5, 6}}; // 前面3个调用有参构造函数,后面7个调用默认构造函数
for (int i = 0; i < 10; i++) {
tb[i].show;
}
在动态内存中创建对象(动态内存分配)
C语言使用malloc等函数来分配动态内存(一旦分配成功,不释放就会持续到进程结束),使用free释放动态内存,C++也可以使用这些函数
Test *pt = (Test *)malloc(sizeof(Test)); // malloc的返回值是void *
但是malloc和free处理自定义类型的时候(类),功能不够完善,仅仅只能分配内存空间,不会自动调用构造函数和析构函数
=========>
C++中提供了两个运算符(new / delete)用来分配和释放动态内存,会自动调用构造函数和析构函数
new 用来分配动态内存,delete 用来释放动态内存((底层仍然是调用了malloc和free))
new 是根据类型来自动的计算需要分配的空间的大小,返回对应类型的指针如果提供初始值,分配空间的时候还能进行初始化动作(调用有参构造函数)
如果不提供初始值,分配空间的时候,数字默认初始化为0,如果是类类型,会调用默认构造函数初始化对象
格式:
类型 *指针名 = new 类型; // 如果是类类型,调用默认构造函数,没有会报错类型 * 指针名 = new 类型(初始值); // 匹配对应的构造函数
类型 *指针名 = new 类型{初始值}; // 建议这样!!! 调用有参构造函数
delete 指针名;
也可以申请一组对象:
类型 *指针名 = new 类型[元素个数];
or
类型 *指针名 = new 类型[元素个数]{初始值};
delete [] 指针名;
int *p = new int; // 仅仅是分配空间 cout << *p << endl; // 0 int *p = new int{1024}; // 分配空间并且初始化 cout << *p << endl; // 1024 delete p; // Test *tp = new Test; // 调用默认构造函数 Test *tp = new Test{1, 2}; // 调用对应的构造函数 tp->show(); delete tp; ================================================= 也可以申请一组对象: 类型 *指针名 = new 类型[元素个数]; or 类型 *指针名 = new 类型[元素个数]{初始值}; delete [] 指针名; // int *p = new int[10]; // 仅仅是分配空间 int *p = new int[10]{1, 2, 3, 4, 5, 6}; for (int i = 0; i < 10; i++) { cout << p[i] << endl; } cout << sizeof(p) << endl; // 64位机:8 delete [] p; // Test *pa = new Test[10]; // 调用10次默认构造函数 Test *pa = new Test[10]{{1, 2}, {3, 4}, {5, 6}}; for (int i = 0; i < 10; i++) { pa[i].show(); } delete [] pa; // 调用10次析构函数
注意:
栈内存是自动管理的,不能使用delete去释放栈上面创建的对象
Test t{100, 101};
delete &t; // ERROR在实际开发中,new和delete一般成对出现,以保证能够及时的释放不再使用的空间,防止内存泄漏
malloc/free和new/delete的区别:
(1) malloc 和 free 不会调用构造函数和析构函数
new / delete 会自动调用构造函数和析构函数
(2) new 是根据类型来自动的计算需要分配的空间的大小,返回对应类型的指针
(3) new可以分配空间并初始化
如果提供初始值,分配空间的时候还能进行初始化动作(调用有参构造函数)
如果不提供初始值,分配空间的时候,数字默认初始化为0,如果是类类型,会调用默认构造函数初始化对象
✔04 布尔类型(bool)
虽然在C语言中也可以使用布尔类型,但是在C++中,布尔类型成为了内置类型之一
类似于int,char、double,...... 是一种基本类型了
布尔类型用于表示数学上面的逻辑概念,它只有两种值:真(1) 和 假(0)
使用true表示真,使用false表示假
布尔类型占用多大的空间呢?sizeof(bool) == 1
理论上来说,bool类型使用 1 bit 就可以表示,为什么会分配一个字节呢?
内存的分配是以字节为单位的,所以 bool类型 至少占用一个字节
作用:
经常用于条件判断和函数返回(从逻辑上来说,bool只有两个值)可以提高程序的可读性
可以提高程序的运行效率
int isEmpty( ... ) {
......
}
bool isEmpty( ... ) {
......
}
布尔类型也可以像数字一样进行运算(很少这样使用),语法不会报错,但是计算结果只能是0或者1
#include <iostream> using namespace std; int main() { bool ok = true; ok = ok + 100; // 1 + 100 = 1 cout << ok << endl; // 1 ok = ok - 1; // 1 - 1 = 0 cout << ok << endl; // 0 // std::boolalpha ---> 用于输出true和false cout << std::boolalpha << ok << endl; // false return 0; }
如果bool类型变量的值为:0,NULL,nullptr,false,值为0的表达式等,都表示假
✔05 explicit关键字
作用:防止构造函数被隐式调用
explicit 关键字是用于修饰构造函数的(一般只修饰只有一个参数的构造函数)语法:
explicit 构造函数名(形式参数列表) {
......
}
表明该构造函数的调用必须是"显示的(明确的)",防止构造函数被隐式调用和它对应的一个关键字叫做 implicit ,意思是"隐式的",表明构造函数可以被隐式的使用
默认情况下,构造函数都是 implicit
隐式转换(一般只修饰只有一个参数的构造函数):
在某些情况下,拷贝构造函数还可以参与隐式类型转换。例如,如果类定义了一个接受单个参数的构造函数,并且这个构造函数没有被声明为 explicit ,那么编译器在需要的情况下可能会使用这个构造函数进行隐式类型转换。不过,这与拷贝构造函数本身并不直接相关,而是涉及到单个参数构造函数的隐式类型转换行为
隐式转换,只有以下三种情况会用到:#include <iostream> using namespace std; class Test { private: int _a; public: Test(int a) { _a = a; cout << "有参构造" << " " << _a << endl; } ~Test() { cout << "无参构造" << " " << _a << endl; } void show() { cout << _a << endl; } }; Test func() { return 2056; // return Test{2506}; } void Fun(Test t) { t.show(); } int main() { // 第一种情况:实例化一个对象 Test t1 = 1024; // Test t1 = Test{1024}; // 会先调用构造函数,再调用拷贝构造函数 t1.show(); // 第二种情况:函数返回 Test t2 = func(); t2.show(); // 第三种情况:参数传递 Fun(100); // Fun(Test{100}); }
06 练习
1. 在画矩形的基础上,实现一个圆类,可以显示到开发板
circle.hpp
circle.cpp
2. 在画矩形的基础上,实现一个BMP图片类型,可以在屏幕上面显示BMP图片
3. 在画矩形的基础上,实现一个JPEG图片类型,可以在屏幕上面显示JPEG图片
4. 考虑实现一个顺序栈类(MyStack),属性和行为自己考虑#include <iostream> #include <string.h> #include <string> using namespace std; #define MAXSIZE 10000 using SElemType = char; // typedef char SElemType; class Stack { private: int m_maxsize; // 栈大小 int m_top; // 栈顶 栈中元素个数 = 栈顶 + 1 SElemType *m_data; // 指向动态分配的数组 public: Stack(int maxsize = 10000) : m_maxsize(maxsize), m_top(-1), m_data(new SElemType[m_maxsize]) { cout << "(1)构造函数" << endl; } // 直接将数组里面的元素入栈 Stack(SElemType *arr, int n, int maxsize) : m_maxsize(maxsize), m_data(new SElemType[m_maxsize]) { cout << "(2)构造函数" << endl; for (int i = 0; i < n; i++) { push(arr[i]); } } // 拷贝构造 深拷贝 Stack(const Stack & s) : m_maxsize(s.m_maxsize), m_top(s.m_top), m_data(new SElemType[s.m_maxsize]) { cout << "(3)拷贝构造函数" << endl; for (int i = 0; i <= m_top; i++) { m_data[i] = s.m_data[i]; } } // 析构函数 ~Stack() { cout << "析构函数" << endl; if (m_data != nullptr) { delete [] m_data; } } // 判断栈是否为空 bool empty() { if (m_data == nullptr || m_top == -1) { return true; } return false; } // 获取栈顶元素的值,但是不出栈 bool top(SElemType & t) { if (m_data == nullptr || m_top == -1) { return false; } t = m_data[m_top]; } // 返回栈元素个数 int size() { return m_top + 1; } // 返回栈的最大容量 int MaxSize() { return m_maxsize; } // 入栈 void push(SElemType d) { if (m_data == nullptr) { return; } // 栈满 if (m_maxsize - 1 == m_top) { SElemType *temp = new SElemType[sizeof(SElemType) * (m_maxsize * 2)]; for (int i = 0; i <= m_top; i++) { temp[i] = m_data[i]; } delete [] m_data; m_data = temp; m_maxsize *= 2; } m_data[++m_top] = d; } // 出栈 库里面的一个是无返回 无参数 void pop() { if (empty()) { return; } m_top--; } }; int main() { Stack s1{3}; // m_maxsize = 3, m_top = -1 // 判断栈是否为空 if (s1.empty()) { cout << "s1 is empty" << endl; // s1 is empty } SElemType t; // 获取栈顶元素的值,但是不出栈 if (s1.top(t)) { cout << "s1 topval is " << t << endl; } else { cout << "s1 is empty" << endl; // s1 is empty } // 入栈 s1.push('1'); // 1 s1.push('2'); // 1 3 s1.push('5'); // 1 3 5 s1.push('8'); // 1 3 5 8 // 获取栈顶元素的值,但是不出栈 if (s1.top(t)) { cout << "s1 topval is " << t << endl; // s1 topval is 8 } // 返回栈元素个数 cout << "栈元素个数 " << s1.size() << endl; // 栈元素个数 4 // 返回栈最大容量 cout << "栈最大容量 " << s1.MaxSize() << endl; // 栈最大容量 6 // 出栈 s1.pop(); // 1 3 5 s1.pop(); // 1 3 s1.pop(); // 1 s1.pop(); // 空 s1.pop(); // 空 SElemType a[10] = {'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}; Stack s2{a, 10, 8}; // m_maxsize = 16, m_top = 9 Stack s3{s2}; // 拷贝构造函数 s2.pop(); // 1 2 3 4 5 6 7 8 9 return 0; }