C++ 类和对象、初始化列表、explicit

类和对象

结构体和类

🌸结构体

//结构体
typedef struct People {
	int age;
	string name;
	string tele;
	int high;
}Peo;
  1. 类可以看成结构体的升级版,C 结构体中只能放成员变量,不能放成员函数,而类里面可以
  2. 上面 People 结构体描述了人的静态属性,如果要让 People 吃喝睡,就需要类中的函数描述该行为(即函数)

🌸什么是类

  1. 类可以比作蓝图,通过类可以造出实物(实例出对象)
  2. 我们常用的 cout 就是一个 ostream 类创建的对象
class People {
public:
	void People_Eat() {
		cout << "people is eating" << endl;
	}
	string People_InitNmae(string str) {
		name = str;
		cout << "people's name is " << name << endl;
	}
	void People_Drink();	//内外定义
private:
	string name;
	int age;
	int high;
	string tele;
}*p1, p2;	//在创建类的同时建立一个类指针 p1 和一个 p2 对象

//在类外定义函数,需要加上作用域 ::
void People1::People_Drink() {
	cout << "people is drinking" << endl;
}
//注意:成员函数如果在类里面定义,可能会被当成内联函数
//建议在 xx.cpp 文件中定义类,在 xx.h 文件中声明类

🌸struct 和 class 区别
C++ 把 struct 看成类,所以从类的角度看,struct 声明变量等于在类里面声明变量,如下

struct Animal {
private:
	string catogory;
	int age;
public:
	//由于 C++ 把 struct 看成类,所以 C++ 中 struct 中也可以声明成员函数
	void animal_eat() {
		cout << "animal is eating" << endl;
	}
};

//C++ 中的一个小细节
struct ListNode {		//C 风格
	int value;
	struct ListNode* next;
};
struct ListNode l;	//C 的结构体类型为 “struct + 结构体名字”

struct ListNode {	//C++ 风格
	int value;
	ListNode* next;
};
//因为 struct 升级成类,ListNode 就是类名,类的类型就是类名
ListNode l;

//C++ 中 struct 和 class 都可以创建类,建议用 class

🌸类域:类的 {} 里面的就是类域的范围

  1. 类里的变量叫成员变量,类里的函数叫成员函数
  2. 类里的变量只是声明而不是定义,本质上看待声明和定义的区别就是有没有为变量创建空间
  3. 在实例化为对象时会创建空间,说明对象里的变量就被定义了

访问限定符

🌸public、protected、private

  1. public:类内外都可以访问类成员变量或函数
  2. protected 和 private:只能在类内访问,protected 和 private 的具体区别以后细谈

🌸访问权限(public、protected、private)的作用域从该访问权限符出现到下一个访问限定符出现为止

🌸建议将成员变量设为私有,好处:

  1. 可以自己控制读写权限
  2. 对于写权限,我们可以检测数据的有效性

🌸class 默认访问权限是 private,struct 默认是 public(为了兼容 C)

struct man {
	int id = 0;
	string name;
	void set_name(string s) {
		name = s;
	}
	
};

class woman {
	int id = 1;
	string name;
public:
	void set_name(string s) {
		name = s;
	}
};
int main() {
	man m;
	woman w;
	cout << m.id;	//0
	cout << w.id;	//error,class 默认访问权限为 private,private 下的成员类外不能访问
	
	//可以通过成员函数访问 private 成员变量
	w.set_name("girl");
}

🌸类的大小

类的大小只计算非静态成员变量,存在内存对齐(和结构体一样)

class People {
public:
	void People_Eat();
	string People_InitNmae(string str);
	void People_Drink();	
private:
	string name;
	int age;
	int high;
}*p1, p2:
cout << sizeof(p2) << endl;		//12

class A {
	static int a;
	int aa;
} AA;
cout << sizeof(AA) << endl;		//4,说明 static 的成员变量不计入类的大小

class B {
	static int b;
	int bb;
	void fun(){}
} BB;
//类里面的函数放在公共代码区,不计算其占用内存
cout << sizeof(BB) << endl;		//4

class C {
	static int c;
	int cc;
	void fun1() {}
	static void fun2() {}
} CC;
cout << sizeof(CC) << endl;		//4,说明 static 修饰的函数也不属于类成员

❗️注意:空类的大小为1,起占位作用,标识一下有这个类,这1个字节不存有效数据

C++ 会对每个空对象分配一个字节内存,用来区分不同的空对象(不同空间地址对应不同空对象)

class empty{};  
sizeof(empty);	//1
empty e1, e2;
cout << sizeof(e1) << ' ' << sizeof(e2);	//1 1

this 指针

🌸this 指针:指向当前对象的一个指针,可以访问对象里面的所有成员

🌸引例

  1. 我们知道在 C++ 中成员变量和成员函数是分开存储的,每个非静态成员函数只会延生一份函数实例(这里先不考虑静态成员函数,下面同理),也就是说多个同类型的对象会共用一块代码
  2. 那么:这一块代码是如何确认是那个对象调用的自己呢
  3. C++ 通过提供特殊的对象指针 this 解决上述问题,this 指针指向被调用的成员函数所属的对象

🌸this 指针的特点

  1. this 指针是隐含在每一个非静态成员函数内的一个形参,对象调用成员函数时,将对象地址作为实参传递给成员函数的 this 形参
  2. 所以对象中不存储 this 指针,this 指针一般存放在栈中
  3. this 指针可以为空
  4. this 指针的类型为 classname* const, 说明指针的指向无法改变

🌸this 指针存放的地方

this 可以简单理解存在栈中,因为编译器把 this 指针作为一个隐含参数传给函数,所以本质上还是参数,形参在哪开辟空间,自然是栈

❗️注意:

  1. this 指针不是对象的一部分
  2. 静态函数没有 this 指针,因为静态函数不属于某个对象

🌸this 指针的应用

class A {
public:
	int a;
	//用途1:当形参和成员变量同名时,可用 this 指针来区分
	void fun(int a) {
		a = a;	//error,编译器会就近原则找 a,此时两个 a 都是形参 a

		//使用 this 指针
		this->a = a;	
		//用实例化对象调用这个函数时,会向函数自动传参的指向这个对象的指针
		//前一个 a 是成员变量里面的 a,后面的 a 是参数 a
	}

	//用途2:在类的非静态成员函数中返回对象本身,可使用 return *this
	A& fun1(A& p) {
		//this = nullptr;		//error,this 一旦指向就不能更改
		this->a += p.a;
		return *this;
		//这里函数的返回类型是引用,如果不用引用的方式返回,相当于返回时创建了一个临时对象
		//这个对象和 p 是不同的另一个对象(匿名对象),那么后续的操作与p就没有关系了
	}
};

void test(){
	A AA;
	AA.fun(10);
	cout << AA.a << endl;
	AA.fun1(AA).fun1(AA).fun1(AA).fun1(AA);		//fun1() 返回的是引用
	cout << AA.a << endl;	//50
	//如果是传值返回,虽然不会报错,但是意思就不一样了(输出为 20)
	//这里和 std 输出流 cout 很像(事实上 cout 返回的就是自己)
}

🌸空指针访问成员函数
C++ 中空指针也是可以调用成员函数的,但是要注意有没有用到 this 指针

class A {
public:
	int a;
	void fun1() {
		cout << "this is fun1" << endl;
	}
	void fun1() {
		cout << "this is fun2" << a << endl;	
		//相当于 cout << "this is fun2" << this->a << endl;
		//这里编译器也会看成 this->a,以确保指向的是当前对象
	}
};
void test() {
	A* AA = nullptr;		//this 指针为 nullptr
	AA->fun1();		//可以运行,因为函数并没有使用 this 指针
	AA->fun2();		//不可以运行
	//因为 fun2 函数中存在对成员变量的使用,而 AA 为空指针,即非法访问
}

🌸总结:定义空指针合法,但是使用空指针不合法

疑问:对于 AA,AA->fun1() 不也是调用空指针吗,为什么不会报错

  1. 因为函数放在了公共代码区,编译器看到这种会直接去公共代码区 call 函数地址,而不会通过空指针去找,即代码存在空指针,但是没有使用

  2. -> 本质是给一个地址去找,但是 func1() 在公共代码区,不用找,直接调用

🌸成员变量在类里面虽然只是声明,但是也可以给定默认值,类似缺省参数

class tmp {
public:
	void display() {
		cout << a << endl;
		cout << b << endl;
	}
private:
	int a = 1;	//注:这不是成员变量的初始化,而是给一个缺省值
	char b = 'b';
};

类的成员函数

  1. 类的六个默认成员:构造和析构函数,拷贝构造和赋值重载,取地址重载(普通和 const)
  2. 类的常见成员函数:const 修饰的成员函数、static 静态成员函数

一、构造函数

  1. 构造函数是一个特殊的成员函数名字与类名相同
  2. 创建类对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次

❗️注意

构造函数的虽然名称叫构造,但是其主要任务并不是开空间创建对象,而是初始化对象

🌸其特征如下:

  1. 构造函数语法:classname() {}
  2. 构造函数,没有返回值也不写 void
  3. 函数名称与类名相同
  4. 构造函数可以有参数,可以发生重载
  5. 在实例化对象时候会自动调用构造,无须手动调用,而且只会调用一次

🌸默认构造函数和默认生成的构造函数

如果我们没有写构造函数,编译器会默认生成一个无参的构造函数(默认生成的构造函数);如果我们自己定义了默认构造函数,那编译器就不会生成构造函数。即我们写了编译器就不生成了

  1. 默认构造函数:无参的构造函数或全缺省的构造函数,默认构造函数只能有一个
  2. 默认构造函数 != 默认生成的构造函数
  3. 默认构造函数是我们自己写的,默认生成的构造函数时编译器提供的
class tmp{
public:
	tmp(){		//无参构造
		cout << "无参构造" << endl;
		name = "张三";
		ID = 0;
	}
	tmp(string a = "张三", int b = 10){		//全缺省构造
		name = a;
		ID = b;
	}		//无参和全缺省构造函数只能存在一个,否则会有歧义
	tmp(string a, int b){	//有参构造
		name = a;
		this->ID = b;
		cout << "有参构造";
	}		//有参和全缺省构造函数可以同时存在,但是要注意满足函数重载的要求
private:
	string name;
	int ID;
	double salary;
	string email;
};

当我们不定义构造函数时系统会默认生成一个无参构造函数,但该函数是空实现,不会对成员变量初始化

class test1{
public:
	test1(){
		cout << "无参构造函数" << endl;
	}
	test1(int a, int b){
		cout << "有参构造函数" << endl;
	}
};
class test2 {
public:
	//没有定义构造函数,相当于编译器定义了 test2() {}
	test1 tmp;	//对象成员变量
	int a;
	int b;
	double c;
};
void test() {
	test2 t;	//打印 无参构造函数,说明调用了 test1 的无参构造函数
	cout << t.a << t.b << t.c;		//随机数,说明没有初始化
}

❗️注意:构造函数一般写在 public 限定符里面

class test{
	a(){
		cout << "无参构造" << endl;
	}
};
void test(){
	test t;		//error,private 限定符下的构造函数不可以访问
}

二、拷贝构造

拷贝构造:创建一个与已有对象一样的新对象

🌸拷贝构造特点:

  1. 拷贝构造函数只有单个形参,该形参是对拷贝对象的引用(一般常用 const 修饰)
  2. 拷贝构造是构造函数的一个重载
  3. 拷贝构造函数的参数有且只有一个而且必须是引用(或者指针),如果是传值会引发递归
  4. 类里面的构造函数不能只有拷贝构造,因为有了拷贝构造函数系统就不会生成默认的构造函数,此时拷贝构造函数也不能创建对象
class example {
public:
	example(){		//构造函数
		age = -1;
	}	
	example(const example& p) {		//拷贝构造
		cout << "拷贝构造\n" << endl;
		age = p.age;	//小细节:这里的 p 可以使用 private 限定符里面的 age
	}
	
	//如果使用传值拷贝
	example(const example p) {}	//error,创建形参时也会调用拷贝构造函数,造成递归
	
private:
	int age;
};
void test() {
	example x1;
	example x2(x1);		//拷贝构造
}

//一个类里面不能只有拷贝构造函数
class test {
public:
	test(const test& t) {}
private:
	int a;
};
int main(){
	test t;		//error
	return 0;
}

🌸深拷贝和浅拷贝

  1. 若自己没有定义拷贝构造函数,系统会生成默认的拷贝构造函数
  2. 默认的拷贝构造函数按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝
  3. 这就造成了深浅拷贝问题
class per {
public:
	int age;
	int* high;
public:
	per(int x, int y){
		high = new int(y);
		age = x;
	}
	per(const per& p){		//浅拷贝
		high = p.high;		//编译器默认拷贝函数实现的代码,只拷贝了地址的值
		age = p.age;
	}		//如果浅拷贝,那么当释放 high 的内存时,因为两个指针指向的是同一块空间,所以会多次释放内存导致程序出错

	per(const per& p){		//深拷贝
		high = new int(*p.high);	//在堆创建重新开辟一块地址,存放 p 中的数据
		age = p.age;
	}
	~per(){				//析构函数中自动释放栈区的内存,但是堆区的内存需要手动释放
		assert(high);
		delete high;
		high = nullptr;
	}
};

🌸构造函数的调用

三种调用方式:括号法、显示法、隐式转换法

class example{
public:
	example(){
		cout << "无参构造" << endl;
	}
	example(int a){
		cout << "有参构造" << endl;
	}
	example(const example& exam){
		cout << "拷贝构造" << endl;
	}
public:
	int a;
	int b;
};
void test(){
	//1. 括号法
	example p1;		//调用无参、系统默认生成、全省的构造函数
	
	example p2(10);		//调用有参构造函数
	//example p2{10}	//也可以这样写
	//PS:int a(1) 这种写法类似括号法调用构造函数,不会报错
	
	example p3(p1);		//调用拷贝构造函数

	//注意:调用系统默认生成的构造函数或无参构造函数时,不要()
	example p4();		//不会报错,但编译器会将其看成函数的声明,并不会创建对象

	//2. 显示法
	example p5;					//调用系统默认生成的构造函数或无参构造函数
	example p6 = example(10);		//有参构造函数的调用
	example p7 = example(p6);		//拷贝构造函数的调用

	//3 .隐式转换法
	example p8 = 10;		//有参构造,相当于 example p8(10)
	example p9 = p8;		//拷贝构造
	
	//当有多个成员时,可以用 {}
	example p10 = {10, 20};		//括号里面的参数应与构造函数一致
}

🌸匿名对象

class example {
public:
	int a = 10;
	string name = "zhangsan";
};
void test(){
	cout << example().a << endl;
	example(10);			//这种对象叫做匿名对象
	//特点:当匿名对象该行的代码执行完后,会立刻销毁该匿名对象

	//注意:不要用拷贝构造函数创建一个匿名对象
	example(p2);	//编译器会认为创建了一个 p2 对象,example(p2) == example p2;
	//对于example(10),编译器会认为 example 是调用构造函数,10 是函数参数
	//对于example(p3),编译器会认为 example 是一个数据类型,即创建一个 p3 对象
}

//其他关于匿名对象的一些细节
class A{
public:
	A(){
		cout << "构造函数";
	}
	A(A& a){
		cout << "拷贝构造";
	}
};
void test(example A);
test(example());	//匿名对象作为函数参数,打印 构造函数
//本来是要打印 拷贝构造 的,但是这里不会,函数参数 A 就是传入的匿名对象,这是编译器对匿名对象的优化

🌸拷贝构造函数调用时机

//1. 使用一个已经创建完毕的对象来初始化一个新对象
example t1;
example t2(t1);		//拷贝构造函数

//2. 值传递的方式给函数参数传值
void test(example t);
test(t1);			//由于形参是临时拷贝,所以会调用拷贝构造函数

//3. 以值方式返回局部对象
example test1(){
	example p1;
	return p1;		
	//p1 是局部对象,当函数运行完后就会被销毁
	//返回 p1 时会创建一个临时对象,其特征和 p1 一样,所以也是拷贝构造
}

example t3 = test1();	//这里是拷贝构造的隐式调用
//注意:上面一共发生了两次拷贝构造(函数返回时和创建t3对象时)

🌸总结

  1. 如果定义了有参构造函数就不在提供默认无参构造,但是会提供默认拷贝构造
  2. 如果定义拷贝构造函数,就不会再提供其他构造函数

三、析构函数

析构函数:在对象生命周期将要结束前系统自动调用, 执行一些清理工作(主要是堆区内存)

🌸析构函数的特点:

  1. 语法: ~classname() {}
  2. 析构函数没有返回值也不写 void
  3. 函数名称与类名相同,在名称前加上符号 ~
  4. 析构函数不可以有参数,不可以发生重载
  5. 在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
  6. 析构函数和构造函数一样,都需要写在 public 限定符后,其本质也是成员函数
class person {
public:
	person(string str = "jam", int age = -1){	//构造函数
		cout << "构造函数的调用" << endl;
		name = str;
		m_age = age;
	}			//当我们不写构造函数时,编译器也会自己写一个构造函数,其函数为 person() {};

	~person(){		//析构函数
		cout << "析构函数的调用" << endl;
	}			//当我们不写析构函数时,编译器也会自己写一个析构函数,其函数为 ~person(){};
	
public:
	string name;
	string lover;
private:
	int m_age;
	int IDcard;
};
int main(){
	person s1;
	cout << s1.name << endl;
	return 0;	//打印 析构函数的调用
}

🌸上述代码分析:

  1. s1 是创建在栈区的一个对象,main 函数执行完毕后会自动释放内存,所以在 main 函数执行完之前就会调用析构函数
  2. 如果在类中没有自己定义析构函数,那么析构函数就是一个空实现,对象中的数据(主要是堆区)会在 main 函数执行完后销毁
  3. 如果在 main 函数 return 0; 前面加个 system(“pause”); 是不能打印出析构函数的调用的,因为此时 main 函数还没有结束

🌸对象成员变量的构造和析构顺序

对象成员:类对象作为另一个类的成员变量

class A {
public:
	A(){
		cout << "A的构造函数" << endl;
	}
	~A(){
		cout << "A的析构函数" << endl;
	}
};
class B{
public:
	B(){
		cout << "B的构造函数" << endl;
	}
	~B(){
		cout << "B的析构函数" << endl;
	}
private:
	A a;
};

//测试其构造和析构顺序
void test(){
	B b;	//A B B A
}

四、静态(staic)成员和函数

静态成员和函数:用 static 关键字修饰的成员变量或函数

🌸静态成员的特点

  1. 静态成员不能给缺省值,必须在类外初始化
  2. 因为在类外初始化时才分配空间,所以不在类外初始化就不能用
  3. 所有对象都共享同一份静态成员变量,在编译之前就创建了内存,存放在静态区
  4. 静态函数没有 this 指针,不能访问非静态成员变量(可以理解为静态函数或者静态变量都只会存在一份,当静态成员函数中使用非静态成员变量时,编译器不知道是修改的哪个对象的成员)
class C {
public:
	C(){
		++a;
	}
	C(const C& c1){
		++a;
	}
	static int get_a(){		//静态成员函数
		b = 4;			//error,不能修改非静态成员变量
		return a;
	}
	static int c;
private:
	static int a;
	int b;
};

🌸静态成员变量的初始化

int main() {
	int C::c = 10;		//error,因为 static 是一个全局的变量,不能在 main 函数中初始化
}

//静态成员变量必须在全局作用域内初始化
int C::a = 10;	//静态成员变量的初始化不受限定符(private)的限制(特例)
int C::c = 10;	//初始化时不用加 static(加了反而报错)

🌸静态成员的访问

因为所有对象共享静态成员,所有静态成员有两种访问方式

class C {
public:
	static int c;
	static int get_a() {		
		return a;
	}
private:
	static int a;
};
int C::c = 10;
int C::a = 10;
int main() {
	C test;
	//1. 通过对象进行访问
	cout << test.c << endl;
	//2. 通过类名进行访问
	cout << C::c << endl;
	
	//虽然静态成员初始化不受限定符限制,但是访问时和普通成员一样有三个级别
	cout << C::a;	//error,private 下的成员变量不能在类外访问
	cout << C::get_a();		//可以通过静态函数访问

	//静态成员函数的调用
	//1.通过对象进行访问
	int a = test.get_a();
	//2.通过类名
	int b = C::get_a();
}

🌸总结

  1. 所有类对象所共享静态成员,其不属于某个具体的实例对象
  2. 静态成员变量必须在类外定义,定义时不添加 static 关键字
  3. 类静态成员可用 classname::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有 public、protected、private 3种访问级别
  6. 静态成员函数不能调用非静态成员函数(相当于在静态成员函数里面不可以使用非静态成员)
  7. 非静态成员函数可以使用静态成员变量和函数

五、const 修饰的成员函数

const 成员函数:成员函数后加 const

🌸特点

  1. const 修饰的成员函数,本质上修饰的是其隐含参数 this 指针
  2. const 成员函数内不可以修改成员变量,因为 this 指针变成了:const classname* const this(常指针)
  3. 类的成员变量在声明时加上关键字 mutable,就可以在 const 修饰的成员函数中修改
  4. 建议如果一个成员函数不需要修改成员变量,就加上 const
class A {
public:
	int a;
	mutable int b;
	void fun1(){
		this->a = 10;
		a = 20;
	}
	void fun2()	const{		//const 修饰的成员函数
		a = 10;		//error,不能更改成员变量
		this->a = 10;		//error
		this->b = 10;	//加上 mutable 后就可以修改
	}		//该函数相当于void fun2(const A* this)
};

🌸常对象:

特点:

  1. 对象声明前加 const 称该对象为常对象
  2. 常对象里面的成员变量不能修改(mutable 修饰的可以)
  3. 常对象只能调用 const 修饰的函数(若调用普通成员函数,如果该函数修改了成员变量,那么相当于侧面修改了成员变量)
void test(){
	const A tmp;		//常对象
	tmp.a = 10;		//error,常对象里面的成员变量不能修改
	tmp.b = 10;		//mutable 修饰的成员变量可以修改
	tmp.fun1();		//error,常对象只能调用 const 修饰的函数
	tmp.fun2();
}

六、取地址操作符重载函数

🌸有普通和 const 修饰两种,这两个函数一般不用定义,编译器默认生成

class tmp {
public:
	tmp* operator&(){		//普通
		cout << "普通" << endl;
		return nullptr;		//让我们无法获得对象地址
	}
	const tmp* operator&() const {		//const修饰
		cout << "const修饰" << endl;
		return this;
	}	
	//注意:这两个函数构成函数重载,因为 this 指针的类型不同
};
int main() {
	tmp a;
	tmp* pa = &a;	//普通
	const tmp b;	
	const tmp* pb = &b;		//const修饰
}

初始化列表

  1. 我们知道通过构造函数,可以给成员变量一个初始值,但是这并不是初始化,只能叫赋初值
  2. 对于一些特殊类型的变量,例如引用、const 修饰和自定义类型(没有默认构造函数),在构造函数体内赋初值的方法是不能实现的
  3. 真正的初始化发生在构造函数语句前,即初始化列表处,所以构造函数在赋初值时需要一次初始化和一次拷贝(赋值)的开销

🌸初始化列表:对成员变量进行初始化操作

语法:构造函数():成员变量1(值1), 成员变量2(值2), …… {}

class init {
public:
	int m_age;
	string name;
	
	//原始的构造函数初始化对象
	init(int age, string str) {
		m_age = age;
		name = str;
	}

	//使用初始化列表
	init() :m_age(-1), name("null") {}

	//还可写成如下,使得更加灵活
	init(int a, string str) :m_age(a), name(str) {}
};

❗注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含引用、const 修饰、自定义类型(该类没有默认构造函数)的成员变量,必须在初始化列表位置进行初始化
  3. 尽量使用初始化列表初始化,因为对于自定义类型成员变量,一定会先使用初始化列表初始化,内置类型没有区别
  4. 成员变量在类中声明的次序就是其在初始化列表中初始化的顺序,与其在初始化列表中的先后次序无关
//对第三点举个例子
class Time{
public:
	Time(int day = 0){
		this->day = day;
	}
public:
	int year;
	int month;
	int day;
};
class date{
public:
	date(int hour, int day){
	//这里没有使用初始化列表,那么 time 里面的 day 就不能设置成我们希望的值		
	//time.day 只能用其默认构造函数初始化为0
		this->hour = hour;
		//time.day = day;	//也可以在构造函数类对其赋值
	}
public:
	int hour;
	int min;
	int second;
	Time time;
};
void test(){
	date d(10, 20);
	cout << d.time.day;	//结果为0,与我们想要的结果不符
}

//使用初始化列表初始化自定义类型
class A{
public:
	string mobile_name;
	A(string str){
		mobile_name = str;
	}
};
class B{
public:
	A a;	//对象成员
	string name;
	B(string str1, string str2) :name(str1), a(str2){}
	//a(str2)等价于A a = str2;	属于构造函数的隐式转换调用
};

//关于第四点的举例
class tmp{
public:
	tmp(int a) :a2(a), a1(a2){}
	void print(){
		cout << "a1=" << a1 << endl << "a2=" << a2 << endl;
	}
	int a1;
	int a2;
};
void test(){
	tmp t(1);
	t.print();		//a1=-88768854   a2=1
	//说明先初始化a1,后初始化a2
}

explicit 关键字

explicit:用该关键字修饰的构造函数,会禁止构造函数的隐式类型转换

class tmp {
public:
	tmp(int){	//占位参数
		cout << "tmp(int)" << endl;
	}
	tmp(int, int){
		cout << "tmp(int, int)" << endl;
	}
};
void test(){
	tmp A = 1;		//隐式类型转换
	tmp B = { 1,2 };		//c++11支持多参数转换,c++98 不支持
}

//使用explicit
class tmp1 {
public:
	explicit tmp1(int a) {}
	explicit tmp1(int a, int b) {}
};
void test(){
	tmp1 A = 1;			//error
	tmp1 B = { 1,2 };	//error
}

本篇文章到这里就结束了,欢迎批评指正!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值