文章目录
- 1.类的6个默认成员函数
- 2.构造函数
- 2.1基本概念和用法
- 2.2初始化列表
- 2.3explicit关键字
- 3.拷贝构造函数
- 4.重载赋值运算符
1.类的6个默认成员函数
如果定义一个空类,其实并不是什么都没有,编译器会默认生成6个默认的成员函数!默认成员函数,如果用户不显式实现,编译器会生成的成员函数称为默认成员函数
class Date {};
2.构造函数
2.1基本概念和用法
构造函数:名字与类名相同,无法返回值的特殊成员函数;创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次,构造函数名字听着像是构造对象的,,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
class student
{
public:student() { cout << "_age value is " << _age << endl;_age = 19;cout << "student()构造函数被调用" << endl; }
private:int _age = 18; // 不是初始化,注意是_age声明!
};int main()
{// 对象(自定义类型变量)示例化,在栈上开辟空间student stu;return 0;
}
注意:
- 构造函数可以被重载,拷贝构造函数和构造函数构成重载
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
- 编译器默认生成的构造含,C++标准规定:对内置类型(如:整形int double 、任何类型的指针等)不做处理、对自定义类型会调用它的默认构造函数(默认构造函数包括:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数)
- C++标准是规定了不做处理,但是标准不是人人都遵守的,VS下有些版本会处理!太狠了!C++11提供了,缺省参数的方式,声明是做了处理,当调用构造函数时会根据缺省值初始化,如:
int _age = 18;
class student
{
public:// 注意这里显示写初始化列表就不去走 int _age = 18;这里的缺省了,太坑了!student() :_age(){ cout << "_age value is " << _age << endl;}int _age = 18;
};
int main()
{student stu;return 0;
}
class Date {
private:int _year;int _month;int _day;public:Date(){_year = 10; _month = 10; _day = 10;}Date(int year = 5, int month = 5, int day = 5){_year = year;_month = month; _day = day;}
};
int main()
{//这里无法通过编译,有两个无参构造函数,产生歧义。(构成重载,但是产生歧义)Date date;return 0;
}
2.2初始化列表
class student
{
public:student() { _age = 19;cout << "student()构造函数被调用" << endl; }
private:int _age = 18; // 不是初始化,注意是_age声明!
};
这里使用缺省参数int _age = 18;
是声明,在student() {_age = 19;}
构造函数中,也不能算是真正的初始化,构造函数体中的语句只能将其称为赋初值!因为初始化只能初始化一次,而构造函数体内可以多次赋值。
真正初始化的地方在初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个**"成员变量"后面跟一个放在括号(会去调用别人拷贝构造)中的初始值或表达式**。
class student
{
public:student(int age = 18):_age(age){ cout << "_age value is " << _age << endl;}
private:int _age;
};
int main()
{student stu1;student stu2(19);return 0;
}
-
注意:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次);尽量优先是初始化列表初始化,对于自定义类型成员变量,一定会先使用初始化列表初始化。
-
类中包含以下成员,必须放在初始化列表位置进行初始化:1.const成员变量 2.引用成员变量 3.自定义类型成员(且该类没有默认构造函数时)
class A
{
public:A(int a):_a(a){}
private:int _a;
};
class B
{
public:B(int a, int ref):_aobj(a), _ref(ref), _n(10){}
private:A _aobj; // 没有默认构造函数int& _ref; // 引用const int _n; // const
};int main()
{B b(1, 2);
}
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:A(int a):_a1(a), _a2(_a1){}void Print() { cout << _a1 << " " << _a2 << endl; }
private:int _a2;int _a1;
};
int main()
{A aa(1);aa.Print();
}
输出结果:
1 -858993460
2.3explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
class Date
{
public:Date(int year, int month = 1, int day = 1):_year(year), _month(month), _day(day){}
private:int _year;int _month;int _day;
};int main()
{Date d1 = 2021;return 0;
}
Date d1 = 2021;
隐式类型转换,调用Date(int year, int month = 1, int day = 1)
;构造函数。
class Date
{
public:explicit Date(int year, int month = 1, int day = 1):_year(year), _month(month), _day(day){}...
};
int main()
{Date d1 = 2021;return 0;
}
在构造函数前加上explicit修饰,不能隐式转换,当然在字符串中,隐式类型转换还是很好的
#include<iostream>
#include<string>
using namespace std;int main()
{// 1.隐式类型转换string str1 = "hello";// 2.调用构造函数,和上面的等价string str2("hello");return 0;
}
3.拷贝构造函数
拷贝构造函数是构造函数的一个重载形式,拷贝构造函数的参数(显示的,有一个隐藏的this)只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
int fun(int num)
{cout << "int fun(int num){}" << endl;return num;
}
上面的的int fun(int num)
函数传参和返回值传值返回,会发生两次拷贝,如果我们**显示实现自定义类的拷贝构造函数Date(const Date& date)
**会更直观。
class Date
{
public:Date(int year=2023, int month = 1, int day = 1):_year(year), _month(month), _day(day){}Date(const Date& date){_year = date._year;_month = date._month;_day = date._day;}int _year;int _month;int _day;
};Date fun(Date date)
{cout << "Date fun(Date date){}" << endl;return date;
}int main()
{Date d1;fun(d1);
}
通过调试fun(d1);
发现会调用两次拷贝构造函数,Date fun(Date date)
一次是传参一次是返回这,所以拷贝构造函数不能使用传值的方式Date(Date date)
方式,在拷贝构造中重复调用拷贝构造,引发无穷递归,但是不用担心,编译器会制止这样的行为!
对Date fun(Date date)
做修改:
Date fun(const Date& date)
{cout << "Date fun(Date date){}" << endl;return date;
}
引用做参数,不会调用拷贝构造函数,提高效率,当然在函数体内部修改对象,出了函数作用域,修改的值也存在;但是上面加上了const说明date
对象不能修改,但外部的const
和非const
对象都能传递。
4.重载赋值运算符
class Date
{
public:Date(int year=2023, int month = 1, int day = 1):_year(year), _month(month), _day(day){}Date(const Date& date){_year = date._year;_month = date._month;_day = date._day;}Date& operator =(const Date& date){if (this != &date){_year = date._year;_month = date._month;_day = date._day;}return *this;}void print(){std::cout << _year << "-" << _month << "-" << _day << std::endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2024, 2, 1);Date d2 = d1;Date d3;d3 = d1;d2.print();d3.print();
}
上面是正确的写法,另外注意:Date d2 = d1;
这里连续的构造和赋值拷贝,我在VS2022编译器下,优化成了一次拷贝构造。
为什么
Date& operator =(const Date& date)
的返回值是Date&,那void和Date能不能行
class
{...void operator =(const Date& date){if (this != &date){_year = date._year;_month = date._month;_day = date._day;}}...
}int main()
{Date d1(2024, 2, 1);Date d2;Date d3;d2 = d1;d3 = d2 = d1;return 0;
}
这行d3 = d2 = d1;
是用void
返回值不能连续的赋值!那使用Date
会怎么样:
class
{...Date operator =(const Date& date){if (this != &date){_year = date._year;_month = date._month;_day = date._day;}return *this;}...
}int main()
{Date d1(2024, 2, 1);Date d2(2021, 2, 1);Date d3;(d3 = d2) = d1;d2.print();d3.print();return 0;
}
输出结果:
2021-2-1
2021-2-1
结果不是我想要的啊!此时由于括号"()"改变了结合顺序,先执行d3=d2,结果是将d2的值赋值给d3,这里是没有问题的!其实d3 = d2 等价于 d3.operator=(d2)
函数返回值如果是非引用返回的右值,引用返回的是左值;这里Date operator =(const Date& date)
非引用返回右值,但是右值充当左值,出现异常!