1. 取地址运算符的重载
1.1 const成员函数
- 使用方法:把const放到成员函数参数列表的后面称为const成员函数;如:void print()const{}
- const实际是对成员函数的隐式指针this进行修饰,表明在该类成员函数里面不能对类的任何成员进行改变,而由于const是对隐式指针this进行修饰,这也意味着只能修饰对象里的成员函数。如下代码进行解释:
#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(const Date* const this) const void Print() const { /*_year = 1;*/ //这段代码是不成立的,因为print成员函数的this指针被const修饰 cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; };
补充:const 修饰Date类的Print成员函数,Print隐含的this指针由Date* const this转变为const Date* const this;所以_year的原形应该是 const this->_year,这也代表了_year的值不能被修改所以_year = 1这个操作无法执行;
1.2 取地址运算符重载
- 当我们想取类实例化对象的地址的时候我们不需要实现取地址运算符重载,因为编译器会帮我们实现;但有些特殊的情况,比如我们不想让别人取到当前类对象地址的时候我们就可以自己实现取地址运算符重载返回一个非法地址;如下代码所示:
#include<iostream> using namespace std; class Date { public: //我们自行重载的取地址符; //无论取哪个都是返回空地址 Date* operator&() { return nullptr; // return nullptr; } private: int _year; // 年 int _month; // ⽉ int _day; // ⽇ }; int main() { Date d1; cout << &d1 << endl; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:;
-
当我们不自主实现编译器就会帮我们实现直接使用&d1就行;如下代码所示:
#include<iostream> using namespace std; class Date { public: 编译器自主实现的代码原形 //Date* operator&() //{ // return this; // // return nullptr; //} private: int _year; // 年 int _month; // ⽉ int _day; // ⽇ }; int main() { Date d1; cout << &d1 << endl; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
2. 构造函数初始化列表
- 之前我们实现构造函数的时候,初始化成员变量都是在构造函数体里实现,现在我们添加了个新概念:“初始化列表”,初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。初始化列表语法(例如实现个Date类):Date(): _year(2010){};代码如下:
#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() const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
- 并且每个成员变量在初始化列表中只能出现一次;语法理解上初始化列表可以认为是每个成员变量定义初始化的地方;
- 有三个变量是必须使用初始化列表进行初始化的:1. 引用成员变量, 2. const修饰的成员变量,3. 没有默认构造函数的类类型变量;这三个如果不在初始化列表进行初始化编译会报错;如下图和代码所示:
正确的代码如下:
#include<iostream> using namespace std; class Person { public: Person(int& high, int CardCode, int age) :_age(age) ,_high(high) { _CardCode = CardCode; } private: int _CardCode; const int _age; int& _high; }; int main() { }
-
C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。如下代码所示:
#include<iostream> using namespace std; class Person { public: Person(int& high, int CardCode, int age) { _CardCode = CardCode; } private: int _high; int _CardCode; //这里并不是赋值,而是给缺省值 const int _age = 20; int& MyHigh = _high; }; int main() { }
-
尽量使用初始化列表初始化,因为没有在初始化列表初始化的成员也会走初始化列表,如果如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有 显示在初始化列表初始化的⾃定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。总结如下图:
补充一点:初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。举个例题:
#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(); }
一般我们直接看着会认为a1先接收a,a1 = 1,然后a2再等于a1所以a2也等于1,但并不然,需要按照声明的顺序,我们先看private域里,a2先声明再声明a1,那么在初始化列表里面就会先走a2的初始化,而这会a1是随机值,所以打印a1的时候是1,但a2打印出来的是随机值;👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 运行结果为:
3.static成员
- 用static修饰成员变量,称之为静态成员变量,在类内创建类外初始化;如下代码所示:
#include<iostream> using namespace std; class Person { public: private: static int _age; }; int Person::_age = 20; int main() { }
-
静态成员变量存放在静态区,不存在具体的对象中。
-
用static修饰成员函数,称之为静态成员函数,静态成员函数里没有this指针(因为不存在具体对象中),并且静态成员函数只能访问静态成员变量还可以访问其他对象里的静态成员变量;如下代码所示:
#include<iostream> using namespace std; class People { public: static int _num; private: }; class Person { public: static void Print() { //这段代码会报错,因为不能访问非静态成员变量 /*cout << "非静态成员变量_CardCode: " << _CardCode << endl;*/ cout << "静态成员变量_age:" << _age << endl; cout << "静态成员变量num_" << People::_num << endl; } private: static int _age; int _CardCode; }; int Person::_age = 20; int People::_num = 30; int main() { Person p1; p1.Print(); return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 运行结果为:
-
静态成员函数不能访问非静态成员的原因是静态成员函数没有this指针;
-
突破类域可以访问静态成员变量和成员函数,但静态成员变量不能在私有区域里;如下代码所示:
#include<iostream> using namespace std; class Person { public: static void Print() { cout << "非静态成员函数Print的调用" << endl; } static int _age; private: }; int Person::_age = 20; int main() { Person::Print(); cout << Person::_age << endl; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 运行结果为:
-
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。
- 关于static使用的OJ题求1+2+3+...+n_牛客题霸_牛客网 代码如下:
#include <algorithm> class Sum { public: Sum() { _ret += _i; _i++; } static int GetNum() { return _ret; } private: static int _ret; static int _i; }; int Sum::_ret = 0; int Sum::_i = 1; class Solution { public: int Sum_Solution(int n) { Sum arr[n]; return Sum::GetNum(); } };
这个在VS2019上是运行不了的,因为这里涉及了变长数组arr[n],这里的思路是用Sum实例化出n个对象,每次实例化一次都会进行一次Sum的操作,而且又因为_ret和_i是静态成员不会被销毁,所以就能间接实现1+2+3+4......+n的操作;
4. friend友元
-
友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到⼀个类的里面。
- 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。代码如下:
#include<iostream> using namespace std; class Person { public: friend void GetCardCode(Person p1); Person(int cardcode = 1) { _CardCode = cardcode; } private: int _CardCode; }; void GetCardCode(Person p1) { cout << "I get your cardcode: " << p1._CardCode << endl; } int main() { Person p1(10086); GetCardCode(p1); return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 运行结果为:
补充说明:友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
-
-
⼀个函数可以是多个类的友元函数。友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
-
注意!:有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
5. 内部类
- 把一个类定义在另一个类里面,这个就称作内部类。内部类其实是个独立的类,跟定义在全局相比只是受外部类类域的限制和访问限定符限制;如下代码可证明:
#include<iostream> using namespace std; class Outside { public: //实现的内部类 class Inside { public: private: int _InsideNum; }; private: int _OutsideNum; }; int main() { Outside o1; cout << "创建的对象大小为:" << sizeof(o1) << endl; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:这个4就是_OutsideNum的大小,这也就证明了Inside的大小不算在Outside创建的对象里面;
-
内部类默认是外部类的友元;代码如下:
#include<iostream> using namespace std; class Outside { public: //构造函数进行初始化 Outside(int n = 3) { _OutsideNum = n; } //实现的内部类 class Inside { public: void Func(const Outside& o1) { cout << "内部类访问外部类的值为:" << o1._OutsideNum << endl; } private: int _InsideNum; }; private: int _OutsideNum; }; int main() { Outside o1(10); //因为受外部类类域的限制所以要用Outside::; Outside::Inside In1; In1.Func(o1); return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
6.匿名对象
- 用类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名(实参)定义出来的叫有名对象
-
匿名对象生命周期只在当前一行,⼀般临时定义一个对象当前用一下即可,就可以定义匿名对象。
#include<iostream> using namespace std; class Person { public: Person(int age = 10) :_age(age) { cout << "Person(int age)" << endl; } ~Person() { cout << "~Person(int age)" << endl; } private: int _age; }; int main() { Person(1); return 0; }
-
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
7.对象拷贝时编译器进行优化
-
现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。
-
如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新⼀点的编 译器对于连续⼀个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化;代码如下:
#include<iostream> using namespace std; class A { public: A(int a = 0) :_a1(a) { cout << "A(int a)" << endl; } A(const A& aa) :_a1(aa._a1) { cout << "A(const A& aa)" << endl; } A& operator=(const A& aa) { cout << "A& operator=(const A& aa)" << endl; if (this != &aa) { _a1 = aa._a1; } return *this; } ~A() { cout << "~A()" << endl; } private: int _a1 = 1; }; void f1(A aa) {} A f2() { A aa; return aa; } int main() { // 传值传参 A aa1; f1(aa1); cout << endl; // 隐式类型,连续构造+拷⻉构造->优化为直接构造 f1(1); // ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造 f1(A(2)); cout << endl; cout << "***********************************************" << endl; // 传值返回 // 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug) // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug) f2(); cout << endl; // 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug) // 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug) // ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,直接变为构造。(vs2022 debug) A aa2 = f2(); cout << endl; // ⼀个表达式中,连续拷⻉构造+赋值重载->⽆法优化 aa1 = f2(); cout << endl; return 0; }
👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇 👇
运行结果为:
END!