1.取地址运算符重载
1.1 const成员函数
a)将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后 ⾯。
b)const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进⾏修改。 const 修饰Date类的Print成员函数,Print隐含的this指针由Date* const this变为const Date* const this。
例如:
class Date{public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// void Print(const Date* const this) constvoid Print() const{cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;};
void Date::Print()
{cout << _year << "/" << _month << "/" << _day << endl;
}int main(){// 这⾥⾮const对象也可以调⽤const成员函数是⼀种权限的缩⼩Date d1(2024, 7, 5);d1.Print();const Date d2(2024, 8, 5);d2.Print();return 0;}
注意:如果Print后面没有定义const,则当Date被const修饰时就存在权限放大,编译器就会报错。
1.2取地址运算符重载
取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载,⼀般这两个函数编译器⾃动⽣成的就可以够我们⽤了,不需要去显示实现。除非一些很特殊的场景,⽐如我们不想让别⼈取到当前类对象的地址,就可以自己实现⼀份,返回⼀个错误或空地址。
class Date
{
public :Date* operator&(){return this;// return nullptr;const Date* operator&()const{return this;// return nullptr;}private :int _year ; int _month ; int _day ;
};
2.初始化列表(构造函数)
a)实现构造函数时,初始化成员变量主要使⽤函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使⽤方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。
例如:
class Time
{
public:Time(int hour):_hour(hour){};void Print()const{cout << _hour << endl;}
private:int _hour;
};int main()
{Time t1(24);t1.Print();return 0;
}
结果:
b)每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
例如:反复定义成员_hour
报错:
c)引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始 化,否则会编译报错。
例如:
class Time
{
public:Time(int hour):_hour(hour){}void Print()const{cout << _hour << endl;}
private:int _hour;
};class Date
{
public:Date(int& x, int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day), _t(12), _rp(x), _n(1){// error C2512: “Time”: 没有合适的默认构造函数可⽤// error C2530 : “Date::_ref” : 必须初始化引⽤// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象}void Print() const{cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;Time _t;// 没有默认构造int& _rp;// 引⽤const int _n; // const
};
int main()
{int i = 0;Date d1(i);d1.Print();return 0;
}
第一这里的Time类作为Date类中的成员变量,初始化的时候,会去调用初始化_t的默认构造函数,但是注意,Time中是没有默认构造函数的哟(前面小编文章“默认构造函数”中有详细讲解,只有不传参,全缺省,编译器自动生成的构造函数,这三类才是默认构造函数),所以这里_t必须在初始化列表中初始化;
第二这里成员变量_rp也必须在列表初始化;第三const修饰的也必须在列表初始化。
注意:这里不是引用成员变量,const成员变量,没有默认构造的类类型变量这三种变量的,成员变量可以放在初始化列表初始化,也可以放在构造函数里面初始化,如上图。
d) C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使⽤的。
例如:
class Date
{
public:Date():_month(2),_day(1){if (_ptr == NULL){cout << "malloc failed" << endl;}for (int i = 0; i < 3; i++){_ptr[i] = i;}}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);
};
e)尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误。
f)初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。
这里通过一个例题来说明:这里打印的结果会是什么呢?1 1吗?
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();
}
结果其实是: 1和一个随机值
首先这里实例化时,传了1作为参数,那是不是_a1初始化为1,_a2再用_a1初始化为1呢?当然不是,这里初始化的顺序是以成员变量申明的顺序初始化的,首先初始化的是_a2,在这个时候_a1还没有初始化,所以_a2就被初始化成了一个随机值,再去初始化_a1,这时通过传入的参数1,就被初始化为1。
3. 类型转换
a)C++⽀持内置类型隐式类型转换为类类型对象,但是需要有相关内置类型为参数的构造函数。
例如:
class A
{
public:A(int a1):_a1(a1){}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的临时对象,再⽤这个临时对象拷⻉构造aa1// 编译器遇到连续构造+拷⻉构造->优化为直接构造A aa1 = 1;aa1.Print();// C++11之后才⽀持多参数转化A aa3 = { 2,2 };aa3.Print();return 0;
}
结果:
这里首先用1构造了一个A的临时变量,再将这对象临时拷贝构造aa1。aa3也是同理。
这里如果是用引用的话:
const A& aa2 = 1;
1创建一个临时变量,再拷贝给aa2,但是临时变量具有常属性,需要const修饰,所以引用要加const,不能使权限放大。(这里小编前面“类与对象(引用)”的文章中有更详细的讲解)。
b)构造函数前⾯加explicit就不再支持隐式类型转换
例如:
class A
{
public:// 构造函数explicit就不再⽀持隐式类型转换explicit A(int a1):_a1(a1){}explicit A(int a1, int a2):_a1(a1),_a2(a2){}void Print(){cout << _a1 << " " << _a2 << endl;}
private:int _a1 = 1;int _a2 = 2;
};int main()
{A aa1 = 1;aa1.Print();A aa3 = { 2,2 };aa3.Print();return 0;
}
报错:
4.友元
a)友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。
class A
{// 友元声明friend void func1(const A& aa);
private:int _a1 = 1;int _a2 = 2;
};void func1(const A& aa)
{cout << aa._a1 <<" "<< aa._a2 << endl;
}
int main()
{A aa;func1(aa);return 0;
}
结果:
如果没有友元声明func1函数是不能访问aa中的私有成员变量。
b)外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
c)友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。(即在private,public中定义都可以)
d)⼀个函数可以是多个类的友元函数。
例如:
// 前置声明,否则A的友元函数声明编译器不认识B
class B;class A
{// 友元声明friend void func(const A& aa, const B& bb);
private:int _a1 = 1;int _a2 = 2;
};class B
{// 友元声明friend void func(const A& aa, const B& bb);
private:int _b1 = 3;int _b2 = 4;
};void func(const A& aa, const B& bb)
{cout << aa._a1 << endl;cout << bb._b1 << endl;
}int main()
{A aa;B bb;func(aa, bb);return 0;
}
这里函数func需要访问aa和bb的私有成员变量,所以就要在A,B中都必须要有友元声明,还要注意,这里在B中进行友元声明时,前面已经有A类,但在A中友元声明时,前面还没有B类,所以就要进行前置声明,否则A的友元函数声明编译器不认识B。
e)友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
例如:
class A
{// 友元类声明friend class B;
private:int _a1 = 1;int _a2 = 2;
};
class B
{
public:void func1(const A& aa){cout << aa._a1 << endl;cout << _b1 << endl;}void func2(const A& aa){cout << aa._a2 << endl;cout << _b2 << endl;}
private:int _b1 = 3;int _b2 = 4;
};
int main()
{A aa;B bb;bb.func1(aa);bb.func2(aa);return 0;
}
结果:
f)友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
例如:
g)友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是B的友元。
h)友元提供了便利。但是友元会增加耦合度,破坏了封装,因此友元不宜多⽤。