C++ 《类与对象》(下)
赋值运算符重
• 当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规
定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编
译报错。
运算符定义
运算符重载是具有特名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
int operator+(int x,int y);//声明 int operator+(int x,int y)//定义 { return x+y; }
但是关于运算符重载函数的声明与定义一定的要求:
// 重载为全局的⾯临对象访问私有成员变量的问题
// 有⼏种⽅法可以解决:
// 1、成员放公有
// 2、Date提供getxxx函数
// 3、友元函数
// 4、重载为成员函数
1.成员公有
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } //private://公开成员 int _year; int _month; int _day; };
2.Date提供getxxx函数
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } int& get()//用这个get函数返回这个类的成员 { return _day; } //private://公开成员 int _year; int _month; int _day; }; int operator+(int x,int y); int operator+(int x,int y) { Date A; return A.get()+y }
3.友元函数
#include<iostream> using namespace std; class Date { friend int operator+(int x,int y);//友元函数 public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int operator+(int x,int y); int operator+(int x,int y) { return x+y; }
4.重载为成员函数
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } int operator+(int x,int y);//成员函数 private: int _year; int _month; int _day; }; int operator+(int x,int y)//定义 { return x+y; }
• 重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
class Date 比特就业课 { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date(const Date& d) { cout << " Date(const Date& d)" << endl; _year = d._year; _month = d._month; _day = d._day; } // 传引⽤返回减少拷⻉ // d1 = d2; Date& operator=(const Date& d)//对应了元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数 { // 不要检查⾃⼰给⾃⼰赋值的情况 if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } // d1 = d2表达式的返回对象应该为d1,也就是*this return *this; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; };
取地址运算符重载
const成员函数
将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后
⾯。
• const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。
const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const
Date* const this
public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // void Print(const Date* const this) const void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { // 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩ Date d1(2024, 7, 5); d1.Print(); const Date d2(2024, 8, 5); d2.Print(); return 0; }
初始化列表
之前我们实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种⽅
式,就是初始化列表,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成
员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。
格式
1、每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。
2、引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错。
public: Date(int& x,int year = 1, int month = 1, int day = 1)//初始化列表 :_year(year) ,_month(month) ,_day(day) ,_n(1) ,_ref(x) {} // void Print(const Date* const this) const void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; const int _n;//常量 int& _ref;//引用 Time _t; // 没有默认构造 };
3、C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
class Date { public: Date() :_month(2) { cout << "Date()" << endl; } void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: // 注意这⾥不是初始化,这⾥给的是缺省值,这个缺省值是给初始化列表的 // 如果初始化列表没有显⽰初始化,默认就会⽤这个缺省值初始化 int _year = 1; int _month = 1; int _day; Time _t = 1; const int _n = 1; int* _ptr = (int*)malloc(12); };
4、尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误。
5、初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。
练习
下⾯程序的运⾏结果是什么()
A. 输出 1 1
B. 输出 2 2
C. 编译报错
D. 输出 1 随机值
E. 输出 1 2
F. 输出 2 1
#include<iostream> using namespace std; class A { public: A(int a) :_a1(a) , _a2(_a1) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a2 = 2; int _a1 = 2; }; int main() { A aa(1); aa.Print(); }
这里考点为:**初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。因为_a2先声明,所以_a2就先初始化,但是由于_a2(_a1),_a1还没初始化,导致_a2成随机值.,所以选 D
类型转换
C++⽀持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数、
构造函数前⾯加explicit就不再⽀持隐式类型转换
#include<iostream> using namespace std; class A { public: // 构造函数explicit就不再⽀持隐式类型转换 // explicit A(int a1) A(int a1) :_a1(a1) {} //explicit A(int a1, int a2) A(int a1, int a2) :_a1(a1) ,_a2(a2) {} void Print() { cout << _a1 << " " << _a2 << endl; } private: int _a1 = 1; int _a2 = 2; }; int main() { // 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3 // 编译器遇到连续构造+拷⻉构造->优化为直接构造 A aa1 = 1;//类型转换,用1先构造在拷贝构造,之后被优化为构造 aa1.Print(); const A& aa2 = 1; // C++11之后才⽀持多参数转化 A aa3 = { 2,2 }; return 0; }