类和对象Ⅲ
🔗接上篇【Ⅱ】默认成员函数篇:构造+析构、拷贝构造+赋值重载、const成员函数、取地址重载、初始化列表
👉【C++入门】类和对象Ⅱ:默认成员函数、构造函数、初始化列表、析构函数、拷贝构造函数、赋值运算符重载函数、const成员函数、取地址及const取地址操作符重载(含 时间计算器 部分实现)
四、补充知识点
类和对象的学习基本收尾,最后补充一些零散的知识点~
4.1 static 静态成员
类中的 static 静态成员,不属于某个对象,而是属于所有对象,属于整个类
用static修饰的成员变量,称之为 静态成员变量;用 static 修饰的成员函数,称之为 静态成员函数。
注意事项:
- 类静态成员变量一定要在类外进行初始化,类中的只是声明。
- 类静态成员函数 没有 this 指针,不能直接访问 非静态成员,因为非静态成员都是属于对象,用 this 调用的
- 类静态成员的访问可通过
类名::静态成员
或者对象.静态成员
一道面试题:实现一个类,计算程序中创建出了多少个类对象。
class A
{
public:
A(int a = 0)
{
++count;
cout << "构造" << endl;
}
A(const A& aa)
{
++count;
cout << "拷贝" << endl;
}
// 静态成员函数 -- 没有 this 指针
static int getCount()
{
// _a++; //err..
return count;
}
// 静态成员变量
static int count; // 声明
};
int A::count = 0; // 静态成员变量 定义初始化
void func(A aa)
{}
int main()
{
A a1; //----> 构造
A a2(1); //----> 构造
A a3 = 1; //----> 构造(编译优化结果,详见上篇 explicit 关键字)
A a4(a1); //----> 拷贝
func(a1); //----> 拷贝
A a5[10]; //----> 构造X10
cout << A::count << endl; //----> 15
cout << a1.count << endl; //----> 15
A* ptr = nullptr;
cout << ptr->count << endl; //----> 15 能运行哦~~虽然是空指针,但是没有解引用
//cout << a1.getCount() << endl; // 对也不对,如果对象创建在函数里就很挫了~
cout << A::getCount() << endl; //----> 15
return 0;
}
oj 题练习:🔗JZ64 求1 + 2 + 3 + … + n
// 描述:求1 + 2 + 3 + ... + n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A ? B : C)。
class Sum {
public:
Sum()
{
_sum += _i;
_i++;
}
static int getSum()
{
return _sum;
}
static int _i;
static int _sum;
};
int Sum::_i = 1;
int Sum::_sum = 0;
class Solution {
public:
int Sum_Solution(int n) {
//Sum a[n]; // C99 才支持变长数组
Sum* ptr = new Sum[n];
return Sum::getSum();
}
};
/*int main()
{
Solution s;
cout << s.Sum_Solution(10) << endl;
return 0;
}*/
在该题中,为了调用函数专门创建了一个对象 s,这种多此一举的举动,可以使用下面这个知识点进行优化~😊
匿名对象
匿名对象,定义时不需要对象名,生命周期只在该行(一次性对象~~)
只需要调用函数,不需创建对象时,我们可以尝试使用它
上面 oj 题中,创建对象,用对象调用函数的写法,可以转换成如下:
cout << Solution().Sum_Solution(10) << endl;
使用案例:
A func(int n)
{
int ret = Solution().Sum_Solution(n);
//A retA(ret);
//return retA;
return A(ret);
}
4.2 友元
友元作为一种突破封装的手段,虽然提供了便利,但是使用它会增加耦合度,破坏封装。不宜多用 ~
友元分为 友元函数 和 友元类
4.2.1 友元函数
当类将一些权限设置为 私有 成员,我们可以通过增设 友元函数,便于对其进行 直接访问
特征:
- 是 定义在类外 的 普通函数
- 不属于任何类,但需要在 类的内部声明,并加 关键字 friend
// 实现:重载<<、>> (通过 Date 类,要求 cin<<d cout<<d 能够直接输入和输出日期)
class Date
{
// 友元函数声明
friend ostream& operator<<(ostream& _cont, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1900, int month = 12, int day = 31)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
cout << d._year << "-" << d._month << "-" << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d) // 注意:没有 const,不然改不了~表现为栈溢出
{
cin >> d._year;
cin >> d._month;
cin >> d._day;
return _cin;
}
int main()
{
Date d;
cin >> d;
cout << d << endl;
return 0;
}
4.2.2 友元类
助记:我当你是朋友,所以把我的所有都跟你共享。你把我当不当做朋友都无所谓。
特征:
- 友元关系是 单向 的
- 友元关系 不具有传递性 (like:a 是 b 的友元,b 是 c 的友元,不能说 a 是 c 的友元)
- 友元关系 不能继承,继承篇会进行详细介绍
例子: 声明 日期类为时间类的友元类,则在日期类中,可以直接访问 Time 类中的私有成员变量
class Time
{
// 友元类声明
friend class Date;
public:
Time(int hour = 11, int minute = 59, int second = 59)
:_hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
4.3 内部类
类的里面再定义一个类。内部类是一个独立的类,不属于外部类的成员,只是受了外部类域的限制,不能通过外部类访问的对象访问内部类的成员。内部类就是外部类的友元类,参见友元的定义。
特性:
- 内部类定义在外部类的 public、protected、private 都是可以的。
- 内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。
- 内部类是独立的类,sizeof(外部类)=外部类,和内部类没有任何关系。
class Out
{
public:
// 【内部类】,In 天生就是 Out 的友元
class In
{
public:
void func(const Out& o)
{
cout << sta << endl;
cout << o.sta << endl;
}
private:
int b;
};
private:
int a;
static int sta;
};
int Out::sta = 1;
int main()
{
cout << sizeof(Out) << endl; // 外部类的大小跟内部类无关
Out::In ii;
ii.func(Out());
return 0;
}
------
输出结果:
4
1
1
重写 oj 题练习:🔗JZ64 求1 + 2 + 3 + … + n(只是为了展示语法)
// 描述:求1 + 2 + 3 + ... + n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A ? B : C)。
// 要求:使用内部类增加保密性
class Solution {
private:
class Sum {
public:
Sum()
{
_sum += _i;
_i++;
}
};
static int _i;
static int _sum;
public:
int Sum_Solution(int n) {
//Sum a[n]; // C99 才支持变长数组
Sum* ptr = new Sum[n];
return _sum;
}
};
int Solution::_i = 1;
int Solution::_sum = 0;
4.4 拷贝对象时的一些 编译器优化
编译器优化原则:一句话里面的多个步骤,才敢考虑优化
我们在此讨论两种优化情况:
- 构造+拷贝构造 —>(优化为)—> 直接构造
- 连续拷贝 —>(优化为)—> 一次拷贝
这里的实验有一本参考书目叫做《深度探索C++对象模型》
void func1(A aa)
{}
void func2(const A& aa) // 临时变量和匿名都是常。加 const 后可调用
{}
A func3()
{
A aa; // 构造(无优化)
return aa; // 拷贝(无优化)
}
A func4()
{
return A(); // 构造+拷贝 --> 直接构造
}
int main()
{
A aa1 = 1; // 构造+拷贝 --> 直接构造
func1(aa1); // 拷贝(无优化)
func1(2); // 构造+拷贝 --> 直接构造
func1(A(2)); // 构造+拷贝 --> 直接构造
cout << "---------------func2-----------------" << endl;
func2(aa1); // 无优化
func2(2); // 构造(无优化)
func2(A(2)); // 构造(无优化)
cout << "---------------func3-----------------" << endl;
func3(); // 构造 拷贝 (均无优化)
A aa2 = func3(); // 构造 拷贝+拷贝 --> 构造 优化为一个拷贝
// 干扰了,不能优化
A aa2_2;
aa2_2 = func3();
cout << "---------------func4-----------------" << endl;
func4(); // 构造+拷贝 --> 直接构造
A aa3 = func4(); // 构造+拷贝+拷贝 --> 直接构造
return 0;
}
------
输出函数调用结果:
A(int a)
A(const A& aa)
~A()
A(int a)
~A()
A(int a)
~A()
---------------func2-----------------
A(int a)
~A()
A(int a)
~A()
---------------func3-----------------
A(int a) ↰
A(const A& aa) |
~A() |
~A() ↲
A(int a) ————————————↰
A(const A& aa) |
~A() ↲
A(int a) ————————————↰
A(int a) |
A(const A& aa) |
~A() |
A& operator=(const A& a) |
~A() ↲
---------------func4-----------------
A(int a)
~A()
A(int a)
~A()
~A()
~A()
~A()
对象返回 总结:
- 接收 返回值对象,尽量以 拷贝构造 的方式接收,不要赋值接收(根据 func3 )
- 函数中返回对象时,尽量返回 匿名对象(根据 func4 和 func3 的对比)
函数传参 总结:
- 尽量使用 const & 传参,这样便不需要依赖优化(根据 func2)
五、再次了解 类和对象🙂
类和对象是现实世界的一个镜像,即抽象类别和实体,而我们学习 C++ 这门语言,需要更多关注的是类和类之间的关系。