目录
(一)再探构造函数
(1)初始化列表
注意点:
(2)缺省值
(3)成员变量初始化逻辑图
(二)类型转换
(1)内置类型 转 自定义类型:=
注意点
(2)内置类型 转 自定义类型:={}
(3)可以用A构造B
(4)加explicit修饰,不支持隐式类型转换
(三)static成员
(1)静态成员变量
注意点
(2)静态成员函数
注意点
(3)oj题
(四)友元
(1)重载输入输出运算符
(2)友元
A.友元函数
特点:
B.友元类
特点:
(3)总结
(五)内部类
(1)特点
(2)修改上面那道oj题
(3)总结
(六)匿名对象
(1)使用场景
(2)特点
(一)再探构造函数
(1)初始化列表
这是每个成员变量初始化的地方,先给个代码看看效果:
class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
这里用我们熟悉的Date类举例,Date构造函数我们以前实现过,现在显式写它的初始化列表,即:
:_member1(parameter1)
,_member2(parameter2)
,_member3(parameter3)
注意点:
-
①2个必须出现在初始化列表的成员变量:引用,const修饰,自定义类型。
-
②还有1个自定义类型成员变量:如果没有默认构造函数(一共3种,可在类和对象(中)一文(1)特点中回顾),就必须有初始化列表。
之前我们有提到过,如果自定义类型没有默认函数,编译器会报错:
class Stack
{
public:
Stack(int capacity)
{
// …
}
private:
STDataType* _arr;
size_t _top;
size_t _capacity;
};
class MyQueue
{
public:
private:
Stack pushSt;
Stack popSt;
};
int main()
{
MyQueue q;
return 0;
}

这种情况下我们除了给Stack类添加默认函数外,还可以显式写MyQueue类的初始化列表。
MyQueue(int capacity)
:pushSt(capacity)
,popSt(capacity)
{}
-
③建议统一使用初始化列表初始化,避免未初始化报错情况。
-
④对象初始化顺序按照声明顺序来,建议 声明顺序 和 初始化列表变量顺序 保持一致。
class Date
{
public:
Date(int month = 1, int year = 1, int day = 1)
:_year(year)
, _day(day)
,_month(month)
{
//…
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
return 0;
}
这里Date类构造函数的 参数列表 和 初始化列表 都是跟 声明顺序 不一样的,经过调试,发现 对象d 中 各成员 初始化顺序与声明顺序一致,以下为调试视频:
initializationList
(2)缺省值
C++支持成员变量在声明的时候给缺省值,这个缺省值会给到初始化列表中对应的变量。
上文我们写Date类,在成员声明的时候没有给缺省值,现在加上:
class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
以下为调试结果:

说明相比 声明缺省值,编译器优先取 构造函数参数 的值,具体逻辑图我们往下看。
(3)成员变量初始化逻辑图

(二)类型转换
(1)内置类型 转 自定义类型:=
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
int main()
{
//原先我们创建对象:A a(1);
A a = 1;
return 0;
}
main函数中,我创建了一个对象a,其中就涉及 int类型 转 A类型的隐式转换。
注意点
-
引用直接构造需要加const修饰,因为涉及临时变量问题。
class B
{
public:
B(int b)
:_b(b)
{}
private:
int _b;
};
int main()
{
const B& b = 1;
return 0;
}
(2)内置类型 转 自定义类型:={}
class C
{
public :
C(int c1,int c2)
:_c1(c1)
,_c2(c2)
{}
private:
int _c1;
int _c2;
};
int main()
{
C c = {1,1};
return 0;
}
调试:

(3)可以用A构造B
我们重载一个B的构造函数:
B(A& a)
:_b(a.get()){}
主函数测试:
A a(1);
B b = a;
调试:

(4)加explicit修饰,不支持隐式类型转换
我们给A的构造函数加上explicit修饰:
explicit A(int a)
:_a(a){}
主函数测试:
A a = 1;
可以发现编译器报错:

(三)static成员
(1)静态成员变量
给A类加上static成员变量:
class A
{
public:
A(int a)
:_a(a)
{
++_count;
}
private:
int _a;
static int _count;
};
int A::_count = 0;
这里我加了_count成员变量,用来记录已创建对象数目。
注意点
-
①静态成员变量一定要在类外初始化,相当于全局变量。具体看到上面的代码最后一行。
-
②存放在静态区,所有对象共有,求sizeof(className)的时候不算它。
调试:

-
③它不走初始化列表,不能声明的时候给默认值。
我给_count在声明的时候加上了默认值:
class A
{
public:
A(int a)
:_a(a)
{
++_count;
}
private:
int _a;
static int _count = 0;
};
编译器报错:

(2)静态成员函数
由于_count受访问限定符的限制,不能直接获取它的值。
cout << A::_count << endl;
编译器报错:

这里我加一个getCnt静态函数,用来获取_count变量的值:
static int getCnt()
{
return _count;
}
调试:

(↑↑↑ 指定类域就可以访问静态函数)
注意点
-
①静态成员函数不会传this指针,不能访问非静态成员。
这里我在return前访问非静态成员变量:
static int getCnt()
{
cout << this->_a << endl;
return _count;
}
编译器报错:

-
②另外,非静态函数可以访问静态和非静态成员。
void test()
{
cout << this->_a << endl;
cout << _count << endl;
}
调试:

(3)oj题
来做一道针对我们当前学的知识点的题目:
求1+2+3+…+n_牛客题霸_牛客网
class Help {
public:
Help()
{
_sum += _n;
_n++;
}
static int get() {
return _sum;
}
private:
static int _sum;
static int _n;
};
int Help::_n = 1;
int Help::_sum = 0;
我创建了一个Help类,sum成员变量用来累加题目所求的总和值,n成员变量充当式子中一个一个的加数。
class Solution {
public:
int Sum_Solution(int n) {
Help arr[n];
return Help::get();
}
};
这里arr[n]用到了变长数组,创建了n个Help对象,实现了_n从1到n的累加。
(四)友元
(1)重载输入输出运算符
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
我们知道重载这个函数需要声明成全局函数(前面类和对象(中)运算符重载特点里),但是我们需要类成员变量的访问权。
我们就可以把这俩函数在Date类里声明成友元函数:
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
// …
}
具体代码看这篇文类和对象:实现Date类默认函数:运算符重载最后一个。
(2)友元
A.友元函数
在类中加一个友元声明,函数就可以访问private成员变量了。
文件Date.h中(错误写法!!!):
class Date
{
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
// …
}
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "please enter the year,month,and day in order:";
in >> d._year >> d._month >> d._day;
assert(d._month > 0 && d._month < 13);
assert(d._day > 0 && d._day <= d.getMonthDays(d._year, d._month));
return in;
}
这种写法拿去TestDate.cpp文件测试使用,会重定义报错。

因此我们把它改为内联函数,使它只有一份。
inline ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
inline istream& operator>>(istream& in, Date& d)
{
cout << "please enter the year,month,and day in order:";
in >> d._year >> d._month >> d._day;
assert(d._month > 0 && d._month < 13);
assert(d._day > 0 && d._day <= d.getMonthDays(d._year, d._month));
return in;
}
或者把声明和定义分离。
//声明在Date.h中
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);
//定义在Date.cpp中
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << '/' << d._month << '/' << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
cout << "please enter the year,month,and day in order:";
in >> d._year >> d._month >> d._day;
assert(d._month > 0 && d._month < 13);
assert(d._day > 0 && d._day <= d.getMonthDays(d._year, d._month));
return in;
}
这个就是类和对象:实现Date类一文里的实现方式。
特点:
-
①外部友元函数可以访问类的所有成员变量(包括private成员变量)。
-
②友元函数是外部的函数,不算作类的成员函数。
-
③友元函数不受访问限定符的限制。我们实现Date类就是把它放在了类开头。
-
④一个外部函数可以成为多个类的友元函数。
我们在这里假设一个类里的函数要成为两个类的友元函数:
class B;
class C;
先得给后两个类声明一下,避免编译器找不到。
class A:
class A {
public:
static void a(const B& b, const C& c);
};
class B:
class B {
friend void A::a(const B& b, const C& c);
public:
B(int b = 2)
:_b(b) {}
private:
int _b;
};
B类有一个private成员变量_b。
class C:
class C {
friend void A::a(const B& b, const C& c);
public:
C(int c = 3)
:_c(c) {}
private:
int _c;
};
C类有一个private成员变量_c。
定义A::a:
void A::a(const B& b, const C& c) {
cout << "b=" << b._b << ",c=" << c._c << endl;
}
输出B类对象和C类对象的值。
B b;
C c;
A::a(b,c);
运行结果:

B.友元类
上例子:B是A的友元类
class A:
class A{
friend class B;
public:
A(int a = 1)
:_a(a){}
private:
int _a;
};
A类有一个private成员变量_a。
class B:
class B{
public:
B(const A& a)
:_b(a._a){}
private:
int _b;
};
B类也有一个private成员变量_b。
A a;
B b(a);
B类可以直接访问A类的_a变量,所以可实现b._b根据_a的值初始化:

特点:
-
①友元类里的成员函数都是另一个类的友元函数。
-
②友元类的关系是单向的,B是A的友元类,但是不代表A是B的友元类。
我们在A类中加个测试函数:
void test(const B& b) {
cout << b._b << endl;
}
编译器立马报错了:

-
③友元类的关系不能传递,B是A的友元类,C是B的友元类,不代表C是A的友元类。
我们写一个C类,C类为B的友元类:
class C{
public:
void test(const A& a) {
cout << a._a << endl;
}
};
编译器报错,C类不是A的友元类,无法访问_a:

(3)总结
友元提供了一种突破类访问限定符封装的方式,但是这会增加耦合度,不宜多用。
(五)内部类
先写一个内部类看看效果:
class A{
public:
A(int a = 1)
:_a(a){}
class B {
public:
static void test(const A& a){
cout << a._a << endl;
}
};
private:
int _a;
};
这里B是A的内部类,所以可以直接访问_a。
A a;
A::B::test(a);
运行结果:

(1)特点
-
①内部类只受 外部类类域 和 访问限定符 限制。
把内部类B放到private下,外部就访问不到了:
class A{
public:
A(int a = 1)
:_a(a){}
private:
class B {
public:
static void test(const A& a) {
cout << a._a << endl;
}
};
int _a;
};
测试代码还跟刚刚一样:
A a;
A::B::test(a);
但是编译器报错了:

-
②内部类默认就是类的友元类。
-
③求类大小的时候,不算内部类里的成员变量。
cout << sizeof(A) << endl;
cout << sizeof(A::B) << endl;
运行结果:
(2)修改上面那道oj题
求1+2+3+…+n_牛客题霸_牛客网
class Solution {
public:
class Help {
public:
Help() {
_sum += _n;
_n++;
}
static int get() {
return _sum;
}
private:
static int _sum;
static int _n;
};
int Sum_Solution(int n) {
Help arr[n];
return Help::get();
}
};
int Solution::Help::_n = 1;
int Solution::Help::_sum = 0;
Help类可以作为Solution类的内部类。
(3)总结
内部类就好像把友元类封装到了一个类里面。
(六)匿名对象
没有名字,直接用类创建对象:
class A{
public:
A()
{
cout << "constructor" << endl;
}
};
int main(){
A();
return 0;
}
运行结果:

(1)使用场景
只想使用一下类里的一个函数就可以使用匿名对象,这里我只是想用一下A类里的test函数:
class A{
public:
A()
{
cout << "constructor" << endl;
}
void test() {
cout << "test" << endl;
}
};
int main(){
A().test();
return 0;
}
运行结果:

(2)特点
-
①这个匿名对象的生命周期随这一语句结束而结束:
给A类加上析构函数方便观察:
~A()
{
cout << "destory" << endl;
}
测试代码:
A().test();
cout << "end" << endl;
运行结果:

可以看到在end输出之前,这个匿名对象就已经完成析构了。
-
②const可延长生命周期。
测试代码:
const A& a = A();
a.test();
cout << "end" << endl;
编译器报错:

需要改一下A::test函数:
void test() const {
cout << "test" << endl;
}
运行结果:

可以观察到这个匿名对象在end输出后才析构,生命周期延长到了进程结束之前。



