小记:静默如初,安之若素
1 构造函数
1. 构造函数和普通函数类似,也可以重载或带有缺省参数。
1 #include <iostream>
2 using namespace std;
3
4 class Student
5 {
6 public:
7 Student(const string& name, int age = 0, int id = 0)//缺省构造函数
8 {
9 cout <<"构造函数1"<<endl;
10 m_name = name;
11 m_age = age;
12 m_id = id;
13 }
14 /*Student(const string &name)//重载构造函数
15 {
16 cout<<"构造函数2"<<endl;
17 m_name = name;
18 m_age = 0;
19 m_id = 0;
20 }*/
21 void who(void)
22 {
23 cout << "My name : "<<m_name<<" , age : "<<m_age<<", num : "<<m_id<<endl;
24 }
25 private:
26 string m_name;
27 int m_age;
28 int m_id;
29 };
30
31 int main()
32 {
33 //在栈区创建单个对象
34 Student s = Student("red1", 22, 12346);
35 s.who();
36
37 Student s2 = Student("red2");
38 s2.who();
39
40 return 0;
41 }
2. 缺省(默认)构造函数
1)如果类中没有定义任何构造函数,编译器会为该类提供一个缺省(无参)构造函数:
1.1)对于基本类型的成员变量不做初始化;
1.2)对于类类型的成员变量,会自动调用相应类的无参构造函数类初始化。
eg:
class A{
public:
int m_i;//基本类型
string m_str;//类类型的成员变量(成员子对象)
};
A a;//会自动调用无参构造创建a对象(以无参的方式创建对象时不能加小括号)
cout << a.m_i <<endl;//未知结果
cout << a.m_str << endl; //一定是空字符串
2)如果类中自己定义了构造函数,无论是否有参数,编译器都不会再提供缺省的无参构造函数了。
1 #include <iostream>
2 using namespace std;
3
4 class A
5 {
6 public:
7 A(void)
8 {
9 cout<< "A:A(void)" <<endl;
10 m_i = 1234;
11 }
12
13 int m_i;
14 };
15
16 class B
17 {
18 public:
19 //编译器会为B类提供类似下面的缺省构造函数
20 //B(void){}
21 int m_j;//基本类型的成员变量
22 A m_a;//类类型的成员变量(成员子对象)
23 };
24
25 int main(int argc, char *argv[])
26 {
27 B b;
28 cout << b.m_j << endl;//未知
29 cout << b.m_a.m_i << endl;//1234
30 return 0;
31 }
~
运行结果:
A:A(void)
1482328496
1234
3. 类型转换构造函数(单参构造函数)
class 目标类型
{
目标类型(源类型...){...}
};
可以实现源类型到目标类型的隐式转换
笔试题:explicit关键字
注:使用explicit 关键字修饰构造函数,可以强制要求这种转换通过显示转换完成。
1 #include <iostream>
2 using namespace std;
3
4 class Integer
5 {
6 public:
7 Integer(void)
8 {
9 cout << "Integer::Integer(void)" << endl;
10 m_data = 0;
11 }
12 //int --> Integer
13 //类型转换构造函数
14 /*explicit*/ Integer(int data)
15 {
16 cout << "Interger::Interger(int) "<<endl;
17 m_data = data;
18 }
19 void print(void)
20 {
21 cout << m_data << endl;
22 }
23
24 private:
25 int m_data;
26
27 };
28
29 int main(int argc, char * argv[])
30 {
31 Integer i;
32 i.print();//0
33 //1) Integer tmp(123)
34 //2) i = tmp
35 i = 123;
36 i.print();//123
37
38 //隐式转换可读性差,推荐显式类型转换
39 //i = (Integer)123;//C风格
40 i = Integer(321);//C++风格
41 i.print();
42 return 0;
43 }
4. 拷贝(复制)构造函数
1)用一个已存在的对象构造同类型的副本对象,会调用该类的拷贝构造函数:
语法形式:
class 类名
{
类名(const 类名& ...){...}//必须常引用
};
eg:
class A{...};
A a1;
A a2(a1);//拷贝构造
eg:
1 #include <iostream>
2 using namespace std;
3
4 class A
5 {
6 public:
7 A(int data = 0)
8 {
9 cout << "A::A(int)" << endl;
10 m_data = data;
11 }
12 A(const A& that)
13 {
14 cout << "A::A(const A&)" << endl;
15 m_data = that.m_data;
16 }
17
18 int m_data;
19 };
20
21 int main(int argc, char * argv[])
22 {
23 A a1(123);
24 A a2(a1);//调用A类的拷贝构造函数
25 cout << a1.m_data << endl;//123
26 cout << a2.m_data << endl;//123
27
28 return 0;
29 }
运行结果:
A::A(int)
A::A(const A&)
123
123
2)如果一个类没有自己定义拷贝构造函数,那么编译器会为该类提供一个缺省的拷贝构造函数:
2.1)对于基本类型的成员变量,按字节复制;
2.2)对于类类型成员变量(成员子对象),调用相应类的拷贝构造函数来初始化;
1 #include <iostream>
2 using namespace std;
3
4 class A
5 {
6 public:
7 A(int data = 0)
8 {
9 cout << "A::A(int)" << endl;
10 m_data = data;
11 }
12 A(const A& that)
13 {
14 cout << "A::A(const A&)" << endl;
15 m_data = that.m_data;
16 }
17
18 int m_data;
19 };
20
21 class B
22 {
23 public:
24 int m_i;//基本类型的成员变量
25 A m_a; //类类型的成员变量(成员子对象)
26 };
27
28 int main(int argc, char * argv[])
29 {
30 B b1;
31 b1.m_i = 123;
32 b1.m_a.m_data = 1234;
33 B b2(b1);//调用B的拷贝构造
34 cout << b1.m_i << " , " << b1.m_a.m_data << endl;//123, 1234
35 cout << b2.m_i << " , " << b2.m_a.m_data << endl;//123, 1234
36 return 0;
37 }
注:一般情况不需要自己定义拷贝构造函数,因为编译器所提供的缺省拷贝构造函数已经很好用了
笔试题:判断缺省拷贝和缺省无参构造函数的个数
eg1:
class A{};//有缺省拷贝和缺省无参两个构造函数
eg2:
class A
{
public:
A(int i);
};// 有缺省拷贝构造函数
eg3:
class A
{
public:
A(const A&){}
};//既没有缺省拷贝,也没有缺省无参构造函数
3)拷贝构造函数调用时机
3.1)用已存在的对象作为同类型对象的构造实参
A a1;
A a2(a1);
A a2 = a1;//和上面等价
3.2) 以对象形式向函数传递参数
void foo(A a){}
foo(a1);
3.3)从函数当中返回对象
A foo(void)
{
A a;
return a;
}
笔试题: 下面代码:函数执行期间一共会调用多少次构造函数 ,把代码的执行流程做标记,即哪个位置调用了哪个构造函数进行标记
1 #include <iostream>
2 using namespace std;
3
4 class A
5 {
6 public:
7 A(void)
8 {
9 cout << " A::A(void) " << endl;//无参构造
10 }
11
12 A(const A& that)
13 {
14 cout << " A::A(const A&)" << endl;//拷贝构造
15 }
16 };
17
18 void func1(A a){}
19
20 A func2(void)
21 {
22 A a;//无参
23 cout << "&a = "<< &a << endl;
24 return a;//拷贝
25 }
26
27 int main(int argc, char * argv[])
28 {
29 A a1;//无参
30 A a2 = a1;//拷贝
31 func1(a1);//拷贝
32 /*func2正常返回a拷贝到临时对象,然后
33 再拷贝给a3,发生两个拷贝,
34 但是因为编译器的优化,让a3直接引用a不发生拷贝*/
35 A a3 = func2();//拷贝
36 cout <<"&a3 = "<< &a3 << endl;
37
38 return 0;
39 }
40
41 //函数执行期间一共会调用多少次构造函数
42 //(6次)
43 //编译方式:g++ -fno-elide-constructors 10cpCons.cpp(去优化编译)
44
45
46 //把代码的执行流程做标记,即哪个位置调用了哪个构造函数进行标记
2. 成员初始化表
1. 语法
class 类名{
类名(形参表):成员变量(初值),...{//构造函数体}
};
eg:
class A{
public:
A(int i):m_i(i),m_j(0){
//m_i = i;
//m_j = 0;
}
int m_i;
int m_j;
};
int num = 0;//初始化 ==》int num; num = 0;(先声明成员变量,再进行赋值操作)
1 #include <iostream>
2 using namespace std;
3
4 class Student
5 {
6 public:
7 //先把成员变量定义出来,再赋初值
8 /*Student(const string &name, int age, int num)
9 {
10 m_name = name;
11 m_age = age;
12 m_num = num;
13 }*/
14
15 //定义成员变量时,同时初始化
16 Student(const string &name, int age, int num):
17 m_name(name), m_age(age), m_num(num){}
18
19 void who(void)
20 {
21 cout << "name : " << m_name <<" age : "<< m_age << " num : " << m_num << endl;
22 }
23 private:
24 string m_name;
25 int m_age;
26 int m_num;
27 };
28
29 int main(int argc, char * argv[])
30 {
31 Student stu("red", 12, 123);
32 stu.who();
33
34 return 0;
35 }
2. 多数情况下适用初始化表和在构造函数体中进行赋初值没有太大差异,可以任选一种方式,但是在某些特殊情况下必须要适用初始化表:
1)如果有类类型的成员变量(成员子对象),而该类有没有无参构造函数,则必须通过初始化表来初始化该变量。
1 #include <iostream>
2 using namespace std;
3
4 class A
5 {
6 public:
7 A(int data)
8 {
9 cout << "A::A(void)" << endl;
10 m_data = data;
11 }
12 int m_data;
13 };
14
15 class B
16 {
17 public:
18 B(void):m_a(1234)//如果不初始化m_a,会报错
19 {
20 cout << "B::B(void)" << endl;
21 }
22 A m_a;//类类型成员变量(成员子对象)
23 };
24
25 int main(int argc, char * argv[])
26 {
27 B b;
28 cout << b.m_a.m_data << endl;//1234
29 return 0;
30 }
2)如果类中包含 “ const ” 或 “引用‘” 型的成员变量,必须要适用初始化表来初始化。
1 #include <iostream>
2 using namespace std;
3
4 int num = 100;
5
6 class A
7 {
8 public:
9 /*A(void)
10 {
11 m_i = num;//赋值,不是初始化
12 m_c = 200;
13 }*/
14
15 A(void):m_i(num), m_c(200){}
16 int &m_i;//成员变量的声明,区分声明和创建
17 const int m_c;
18 };
19
20 int main(int argc, char *argv[])
21 {
22 A a;
23 cout << a.m_i << " , " << a.m_c << endl;
24
25 return 0;
26 }
3)初始化表相关笔试题
注:成员变量的初始化的顺序由声明的顺序决定,而与初始化表的顺序无关,不要用一个成员变量去初始化另一个成员变量。
1 #include <iostream>
2 #include <cstring>
3 using namespace std;
4
5 //该类有什么问题,如何去改正
6 class Dummy
7 {
8 public:
9 //Dummy(const char *psz)
10 // :m_str(psz), m_len(m_str.size()){}
11 Dummy(const char *psz)
12 //:m_str(psz), m_len(strlen(psz)){}
13 :m_str(psz?psz:""),
14 m_len(strlen(psz?psz:""))//这样写可以处理异常
15
16 //初始化变量时的顺序和声明有关,和初始化表的顺序无关
17 //size_t m_len;
18 string m_str;
19 size_t m_len;
20
21 };
22
23 int main(void)
24 {
25 Dummy dy("hello world!");
26 //Dummy dy(NULL);error:strlen(出现段错误)
27 cout << dy.m_str << " , " << dy.m_len << endl;
28 return 0;
29 }