目录
1 初识构造函数
1.1 特点与定义
C++ Primer中给了构造函数的定义:类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数(constructor)
简单来说就是用来初始化类对象的数据成员的函数
构造函数的特点:
1.构造函数与类名相同
2.构造函数没有返回类型
其他类似于其他的成员函数:有一个(可能为空的)参数列表和一个(可能为空的)函数体,一个类也可以包含多个构造函数,与其他重载函数相似
注意:不同于其他成员函数,构造函数不能被声明成const
(原因是,const修饰类的成员函数的主要作用是令该函数不能修改数据成员,虽然构造函数也是成员函数,但构造函数内部会修改数据成员的值,所以不能使用const)
1.2 合成的默认构造函数
默认构造函数:控制默认初始化过程,无须任何实参
如果一个类没有显式的定义一个构造函数,那么编译器就会自己生成一个默认构造函数,我们又可以叫它“合成的默认构造函数”
合成的构造函数将按照以下规则初始化类的数据成员:
- 如果存在类内的初始值,就用它来初始化成员 :
class Student
{
//类内的初始值
//在定义时就已经赋好初值
int age = 18;
};
- 否则就默认初始化:
默认初始化:
定义于任何函数体之外的内置类型(int、double....)变量被初始化为0
(我理解的任何函数体外的变量:全局变量)
定义于函数体内部的内置类型变量则不被初始化,此时调用就会出错
#include <iostream>
//函数体外的内置类型变量
int val1;
int main()
{
//函数体内的内置类型变量
int val2;
//此时输出:0
std::cout << val1 << std::endl;
//这一行会导致编译报错(val2未初始化)
std::cout << val2 << std::endl;
}
(可以说是这个合成的默认构造函数基本啥都没有做........)
我们不能依赖于合成的默认构造函数:虽然编译器会自动生成默认函数,但有时候这个默认函数基本没有什么用,因为类中的数据成员如果没有在定义的时候就赋初值,那么基本都是未初始化的数据成员,这并不是我们想看到的,我们需要自己来定义我们需要初始化成员的构造函数
注意:只有当类没有声明任何构造函数的时候,编译器才会自己生成默认构造函数,也就是说,如果我们自己定义了构造函数,此时类内就没有默认构造函数了,我们需要自己定义
1.3 定义构造函数的几种方式
自己写的默认构造:
Student() = default;
Student(){}
ps.在C++11新标准中,如果我们需要默认行为,我们可以在参数列表后面写上=default(默认)来要求编译器生成构造函数
构造函数的初始化列表:
class Student
{
string _name;
int _age;
//可以生成多个参数不同的构造函数(像重载一样
//冒号即后面的部分被我们叫做“初始化列表”
Student(string name):_name(name){}
Student(string name,int age):_name(name),_age(age){}
};
不使用初始化列表,在构造函数的函数体中定义:
class Student
{
string _name;
int _age;
Student(string name,int age)
{
//赋值
_name = name;
_age = age;
}
};
当然也可以和普通成员函数一样在类内声明,在类外定义(记得使用::域操作符!):
class Student
{
public:
int _age;
//默认构造
Student() = default;
//有参构造函数的声明
Student(int age);
};
//有参构造函数的定义
Student::Student(int age)
{
_age = age;
}
//可以使用函数体内定义,也可以使用初始化列表
Student::Student(int age):_age(age)
{}
注意:有些题会考初始化的定义,只有在初始化列表中初始化的值才叫初始化,在函数体内赋值的只是赋值
2 再探构造函数
2.1 构造函数的初始化列表
从上面我们可以了解到初始化列表是怎么使用的,但是在构造函数函数体内直接给值赋值不也是一样的吗?我们为什么要使用初始化列表呢?
先来看看初始化和赋值的区别:
//这个叫做定义并初始化
int val = 1;
//这个叫做定义并默认初始化后赋值
//(int num时进行默认初始化了)
int num;
num = 1;
虽然可以达到同样的效果,让val和num都等于1,但是如果运用到特殊的类型上,定义并赋值并不可取,例如:
const int val = 1; √
const int val;
val = 1; ×
const修饰的常量无法被赋值!!
还有(引用):
int val = 1;
int& num = val √
int& num; ×
num = val;
引用必须在定义时就初始化好!
所以我们必须在成员定义时就进行初始化 或 使用初始化列表!!
结论:如果(非静态成员!)成员是const、引用或某种未提供默认构造函数的类类型,我们必须要通过初始化给他们提供初值
tips:你可能会认为成员初始化的顺序是由初始化列表决定的,其实并不是这样的,在初始化列表中,成员变量的初始化顺序是由它们的定义顺序决定的
class My { int i; int j; public: //使用初始化列表的构造函数 My(int val): j(val),i(j) {} };
上方这个代码会出错!因为按照定义顺序来初始化成员时,i因为是先被定义的,所以需要先被初始化,但i依赖于j的初始化,此时j并未初始化,所以这个代码会出错,i使用了一个未初始化的变量来初始化自己
所以最好不要在初始化的时候引用其他成员来初始化
最好初始化列表的顺序和定义顺序相同,这是一个好的编程习惯
2.2 委托构造函数
C++11新标准扩展了一个叫做委托构造函数的功能,一个委托构造函数可以使用它自己这个类的其他构造函数来帮助它完成它自己的初始化过程。
简单点来说就是找同公司的一个特别能干的来帮你干活....
看看下面的代码:
class Student
{
public:
//受委托的构造函数
Student(string s,int i,double d):name(s),age(i),grade(s)
{}
//委托构造函数
Student():Student("",0,0.0) {}
//即是委托构造,也是受委托的(受下边这个委托)
Student(string s):Student(s,0,0.0) {}
//这里的委托构造函数是拜托上面这个构造函数帮它完成
Student(string s,double d):Student(s)
{
//对grade进行赋值
grade = d;
}
private:
//省略
};
三个参数的构造函数承受了太多.......
2.3 隐式的类型转换
如果一个构造函数只接受一个实参,那么它实际上定义了转换为此类型的隐式转换机制
例如:我们写一个传Student类对象的函数
void ReferStudent(Student s)
{
//省略
}
这个Student类中的构造函数如下所示:
class Student
{
public:
//传一个string的构造函数
Student(string n):name(n){/*....*/}
//传一个int的构造函数
Student(int val):age(val){/*....*/}
};
如果向下面一样调用这个ReferStudent函数,那么就会发生隐式类型转换
/* void ReferStudent(Student s); */
string name = "张三";
ReferStudent(name); //string类型转换成Student
int val = 18;
ReferStudent(val); //int类型转换成Student
//注意:只允许一步的类型转化
//下面这个调用是错误的
ReferStudent("李四");
//这里需要先把 "李四" 转换成 string类型再转换成Student,禁止两次转换
根据上面代码,我们可以看成 在使用这个函数时,如果传入的是string,就调用有一个string参数的构造函数,生成一个临时Student对象到ReferStudent参数列表中,所以我们想要见到这种隐式类型转化需要满足几个条件:
1.有一个参数的构造函数 Student(String s) //省略
2.有一个参数列表中有这个类对象的普通函数 void ReferStudent(Student s);
3.传入这个参数的类型 ReferStudent(str);
此时这个str会根据这个构造函数自动生成一个临时Student对象传给ReferStudent
如果我们不想让这个函数隐式转换也是可以的
我们可以把构造函数声明为explicit:
class Student
{
public:
//让我们改变一下
explicit Student(string n):name(n){/*....*/}
explicit Student(int val):age(val){/*....*/}
};
此时:
string name = "张三";
ReferStudent(name); //错误!string构造函数是explicit的
int val = 18;
ReferStudent(val); //错误!int构造函数是explicit的
注意:explicit关键字只能出现在类内的构造函数声明处!
当然你如果还想直接用string或int传进这个函数也是可以的,我们需要显式类型转换,像下面这样:
//显式类型转换
string name = "张三";
ReferStudent(Student(name));
int val = 18;
ReferStudent(Student(val));