目录
三、类和对象(中)
6、取地址运算符重载
1、const成员函数
2、取地址运算符的重载
四、类和对象(下)
1、深入构造函数
2、类型转换
3、static成员
4、友元
三、类和对象(中)
6、取地址运算符重载
1、const成员函数
- 将const修饰的成员函数称之为const成员函数,const修饰成员函数放到成员函数参数列表的后面。
- const实际修饰该成员函数隐含的this指针指向的对象,表明在该成员函数中不能对类的任何成员进行修改。没有修改的话就可以加,修改的话就不可以加const。
- const 修饰Date类的Print成员函数,Print隐含的this指针由 Date* const this 变为 const Date* 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; } //若不加上const,则为void Print(Date* const this),this指针中的const修饰的是指针不能被改变 // 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); //&d -> Date* d1.Print(); const Date d2(2024, 8, 5); //&d -> const Date* d2.Print(); return 0; }
总结:一个成员函数,不修改成员变量的建议都加上。
更多详细例子可以去看博主的C++_类与对象(中篇)的日期类实现中的const修饰:C++_类和对象(中篇)—— 运算符重载、赋值运算符重载、日期类实现-CSDN博客
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 ; // ⽇
};
四、类和对象(下)
1、深入构造函数
- 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有⼀种方式,就是初始化列表,初始化列表的使用方式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后面跟⼀个放在括号中的初始值或表达式。
- 每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
- 引⽤成员变量,const成员变量,没有默认构造的类类型成员变量,这三种变量必须放在初始化列表位置进⾏初始化,否则会编译报错。
#include<iostream> using namespace std; class Time { public: Time(int hour) :_hour(hour) { cout << "Time()" << 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) ,_ref(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& _ref; // 引⽤ const int _n; // const }; int main() { int i = 0; Date d1(i); d1.Print(); return 0; }
- C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
- 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
- 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持⼀致。
#include<iostream> using namespace std; class Time { public: Time(int hour) :_hour(hour) { cout << "Time()" << endl; } private: int _hour; }; 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); }; int main() { Date d1; d1.Print(); return 0; }
初始化列表总结:
- 无论是否显示写初始化列表,每个构造函数都有初始化列表;
- 无论是否在初始化列表显示初始化,每个成员变量都要走初始化列表初始化;
思考:下面我们来看一道程序编程题目
#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();
}
2、类型转换
- 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; } int Get() const { return _a1 + _a2; } private: int _a1 = 1; int _a2 = 2; }; class B { public: B(const A& a) :_b(a.Get()) {} private: int _b = 0; }; //内置类型->自定义类型的转换(隐式类型的转化) //自定义类型->自定义类型转换(使用构造函数) int main() { // 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3 // 编译器遇到连续构造+拷⻉构造->优化为直接构造 A aa1 = 1; aa1.Print(); const A& aa2 = 1; //拷贝的值放在临时变量中,具有常性,需要用const修饰,才能通过编译 // C++11之后才⽀持多参数转化 A aa3 = { 2,2 }; // aa3隐式类型转换为b对象 // 原理跟上⾯类似 B b = aa3;//正常来说是不可以的 const B& rb = aa3; //拷贝的值放在临时变量中,具有常性,需要用const修饰,才能通过编译 A aa3 = {1,1}; const A& aa4 = {1,1}; //这是调用多参数的形式,本质都是隐式类型的转化 return 0; }
3、static成员
- 用static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进行初始化。
- 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
- 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
- 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。
- 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
- 突破类域就可以访问静态成员,可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
- 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。
// 实现⼀个类,计算程序中创建出了多少个类对象? #include<iostream> using namespace std; class A { public: A() { ++_scount; } A(const A& t) { ++_scount; } ~A() { --_scount; } static int GetACount() { return _scount; } private: // 类⾥⾯声明 static int _scount; }; // 类外⾯初始化 int A::_scount = 0; int main() { cout << A::GetACount() << endl; A a1, a2; A a3(a1); cout << A::GetACount() << endl; cout << a1.GetACount() << endl; // 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明) //cout << A::_scount << endl; return 0; }
思考:求1+2+3+...+n_牛客题霸_牛客网
既然题目要求不能使用这些方法,即循环累加、递归、等差数列公式等思路也行不通,C++解题模式下中提供的解决方案:使用Sum类和Solution类来解决,如下:
class Sum
{
public:
Sum()
{
_ret += _i;
++_i;
}
static int GetRet()
{
return _ret;
}
private:
static int _i;
static int _ret;
};
int Sum::_i = 1;
int Sum::_ret = 0;
class Solution
{
public:
int Sum_Solution(int n)
{
// 变⻓数组
Sum arr[n];//VS编译器不能通过
//VS编译器可通过的写法:Sum* arr = new Sum[n];
return Sum::GetRet();
}
};
思考:
设已经有A,B,C,D 4个类的定义,程序中A,B,C,D构造函数调用顺序为?(选E)
设已经有A,B,C,D 4个类的定义,程序中A,B,C,D析构函数调用顺序为?(选B)
//以下为选项:
A:D B A C
B:B A D C
C:C D B A
D:A B D C
E:C A B D
F:C D A B
//题目具体代码:
class A
{
public:
A()
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" <<enl;
}
};
class B
{
public:
B()
{
cout << "B()" << endl;
}
~B()
{
cout << "~B()" <<enl;
}
};
class C
{
public:
C()
{
cout << "C()" << endl;
}
~C()
{
cout << "~C()" <<enl;
}
};
class D
{
public:
D()
{
cout << "D()" << endl;
}
~D()
{
cout << "~D()" <<enl;
}
};
C c;
int main()
{
A a;
B b;
static D d;
//构造:局部的静态,只有第一次找到初始化,才能够调用
//析构:先析构局部变量(后面的先析构),后析构全局变量。
return 0;
}
思路要点:
- 构造:局部静态变量,只有第一次找到初始化,才能够调用。
- 析构:先析构局部变量(后面的先析构),后析构全局变量。
VS运行编译结果:
4、友元
- 友元提供了⼀种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到⼀个类的里面。
- 外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
- ⼀个函数可以是多个类的友元函数。
- 友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
- 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
- 友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
- 有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用(项目程序中希望是低耦合和高内聚)。
#include<iostream> using namespace std; // 前置声明,都则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; }
#include<iostream> using namespace std; 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.func1(aa); return 0; }