目录
一.构造函数的补充
在上一篇文章类与对象(中)中我们就初次认识了构造函数,但是那篇文章中我们只学了百分之七十左右的内容,在这边文章中我们将对它进行完善。在那篇文章中是不是没看懂为什么对于自定义类型(类,结构体,Union等关键字自己定义的类型),如果这个成员没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要用初始化列表才能解决。那么初始化列表是什么呢?
之前学习构造函数的时候,初始化成员都是在函数体里赋值的,但他还有另一种方式——利用初始化列表。初始化列表的使用形式是以一个冒号开始,接着是以一个逗号分隔的数据成员列表,每个“成员变量”后面都跟一个放在括号里面的初始值或表达式。
初始化列表的注意事项:
1.每个成员变量在初始化列表只能出现一次,初始化列表也可以认为是每个成员变量定义初始化的地方。
class Data
{
public:
Data(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{
}
//也可为
/*Data()
:_year(1)
,_month(1)
,_day(1)
{
}*/
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data d1;
d1.Print();
return 0;
}

2.引用成员变量、const成员变量、没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则编译器会报错。
class Time
{
public:
Time(int hour)
:_hour(hour)
{
//如果调用了Time的实例化对象的默认默认构造函数将会打印Time()
cout << "Time()" << endl;
}
private:
int _hour;
};
class Data
{
public:
Data(int &x,int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
,_t(12)
,_ref(x)
,_n(1)
{
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;//无默认构造函数(不需要传参的)
int& _ref;//引用必须初始化
const int _n;//const把变量变成常变量
};
//若不在初始化列表初始化
//错误 C2512 “Time” : 没有合适的默认构造函数可用
//错误 C2530 “Data::_ref”: 必须初始化引用
//错误 C2789 “Data::_n”: 必须初始化常量限定类型的对象
int main()
{
int i = 0;
Data d1(i);
d1.Print();
return 0;
}
3.C++11支持在成员变量声明给缺省值,主要是给没有显式在初始化列表初始化的成员用的。
猜猜下面下面代码中用构造函数1的结果是什么?用构造函数2的结果又是什么?
class Data
{
public:
//1.
Data()
{
}
//2.
/*Data()
:_year(2)
,_month(3)
,_day(4)
{
}*/
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
//成员声明
int _year = 1;
int _month = 2;
int _day = 3;
};
int main()
{
Data d1;
d1.Print();
return 0;
}
答案就是1: 1/2/3 2: 2/3/4
当成员在声明位置给了缺省值,但没有显式的在初始化列表初始化,那么初始化列表会用这个缺省值初始化,如果没有给缺省值,也没有显式的在初始化列表初始化,那么内置类型成员是否初始化取决于编译器,而自定义类型成员如果没有默认构造函数,编译会错误。
4.初始化列表中按照成员变量在类中的声明顺序进行初始化,和成员在初始化列表中出现的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
考察一下:
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 随机值
程序先给_a2初始化,但_a1还没初始化,_a1在vs中为随机值,_a1初始化为a,a=1,所以_a1初始化为1。
总结:
无论是否写初始化列表,每个构造函数都存在初始化列表。
无论是否在初始化列表显示初始化(还没开空间),每个成员变量都要走初始化列表。
二.类型转换
1.C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
2.构造函数前面加explicit就可以不再支持隐式类型转换。
//首先我们先了解隐式类型转换
//short a='s';//'s'是char类型被转换成了short类型
//int j=0;
//double d=j;//j变量是int类型的转变成了double类型
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;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa1
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1;
// C++11之后才⽀持多参数转化
A aa3 = { 2,2 };
return 0;
}


内置类型转变成类类型也是一样的,只是编译器会直接优化,就不再需要临时变量,也就没有不需要在调用拷贝构造了,直接进行构造函数。
3.类类型的对象之间也可以隐式转换,需要相应的构造函数的支持。
class A
{
public:
A(int a1)
:_a1(a1)
{}
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()
{
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上⾯类似
B b = aa3;
const B& rb = aa3;把A类类型转变成常引用
return 0;
}
三.Static成员
在C语言的时候我们知道static修饰的变量或函数会失去外部链接属性,它的作用在C++中也适用。
1.用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化(类中的构造函数无法初始化静态变量)。即类里面声明,类外面初始化但要指定类域。
2.用static修饰的变量为所有类对象所共有,不属于某个具体的对象,不存在对象中,存放在静态区。⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
3.静态成员函数没有this指针。
4.突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数(初始化时)。
5.静态成员也受public、protected、private访问限定符的限制。
6.静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数构造初始化列表的,静态成员不属于某个对象,所以不走初始化列表。
class C
{
public :
C()
{
_a++;
}
C(const C& c)
{
_a++;
}
~C()
{
_a--;
}
static int get()//没有this指针
{
return _a;
}
private:
//类内声明
static int _a;//不能给缺省值,不走初始化列表
};
//类外初始化
int C::_a = 0;
int main()
{
cout << C::get() << endl;
C c1;
C c2;
C c3(c2);
cout << C::get() << endl;//cout<<c3.get()<<endl;
}
猜猜输出了什么?
输出了0 3,这里也说明了staic修饰的变量只初始化一次。
那static修饰的类什么时候调用析构和构造呢?
判断一下代码A,B,C,D类的实例化对象析构和构造顺序:
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
析构顺序:B A D C(main函数中局部(先构造的后析构)先析构再析构static类(程序退出时才析构)再全局)
构造顺序:C A B D (main函数之前全局变量已经构造好了main函数里的变量就顺着构造)
四.友元(friend)
再上一篇文章类与对象(中)的输出(<<),输入(>>)重载时,它们不能放在类里面定义它的运算符重载的函数,而是要放在全局变量中去定义,当我们在全局定义的时候需要访问类中的成员变量,但是成员变量受类访问限定符限制,我们就用到了友元在类中声明函数。
但它还有很多注意事项:
1.友元是一种突破类访问限定符封装的方式,友元分为友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放在一个类里面。
2.在外部定义的友元函数并不是类的成员函数。
3.友元函数可以在类中的任意地方声明,一般习惯在public之前,不受类访问限定符限制。
4.一个函数可以有多个类的友元函数。
class B;//重点,class A在classB之前,要先声明classBB
class A
{
friend void func(const A& _a, const B& _b);
public:
A()
:a(3)
{
}
private:
int a;
};
class B
{
friend void func(const A& _a, const B& _b);
public:
B()
:b(2)
{
}
private:
int b;
};
void func(const A& _a, const B& _b)//class A和class B的友元函数
{
cout << _a.a << " " << _b.b;
}
int main()
{
A a;
B b;
func(a,b);
return 0;
}
5.友元类中的成员函数都可以是另一个类的友元函数,可以访问另一个类中的私有和保护成员。
class B;
class A
{
public:
A()
:a(3)
{
}
void func(const B& b);//声明但func函数是A类的成员函数
private:
int a;
};
class B
{
friend void A::func(const B& _b);//友元函数声明
public:
B()
:b(2)
{
}
private:
int b;
};
void A::func(const B& _b)//A类中的成员函数是B类的友元函数
{
cout << _b.b << endl;
}
int main()
{
A a;
B b;
a.func(b);
return 0;
}
6.友元类的关系是单向的,不具有交换性,即A类是B类的友元,但是B类不是A类的友元。也没有传递性,如果A是B的友元,B是C的友元,但不代表A是C的友元。
class B;
class A
{
public:
A()
:a(3)
{
}
friend class B;
private:
int a;
};
class B
{
public:
int& func(A& _a)
{
return _a.a;
}
B()
:b(2)
{
}
private:
int b;
};
int main()
{
A a;
B b;
cout << b.func(a) << endl;
return 0;
}
在A类中声明了B是A的友元,那么B类就可以访问A类中的私有和保护成员。
五.内部类
1.一个类定义在另一个类的内部,这个内部的类就是内部类。内部类是一个独立的类,跟定义在全局的相比,它只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包括内部类。即计算一个类的空间大小时内部类的空间不计算在内。
class A
{
public:
A()
{
}
class B
{
public:
B()
{
}
private:
int b;
};
private:
int a;
};
int main()
{
A a1;
int ret = sizeof(a1);
cout << ret << endl;
return 0;
}

2.内部类默认是外部类的友元类。
class A
{
public:
A()
:a(5)
,c(2)
{
}
class B
{
public:
B(const A&a)//B可以访问A的私有的成员变量,B是A的友元
:b(2)
{
b = a.a;
}
void print()
{
cout << b << endl;A
}
private:
int b;
};
private:
int a=5;
int c;
};
int main()
{
A a1;
A::B b1(a1);
b1.print();
return 0;
}
3.内部类本质上也是一种封装,当A类跟B类紧密关联时,A类实现出来主要就是给B使用的,那么可以考虑把A设计成B的内部类,如果放在private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
思路:实例化n个对象,调用构造函数的时候在构造函数中进行相加。
class Sum
{
public:
Sum()
{
sum+=ref;
ref++;
}
static int GetRet()
{
return sum;
}
private:
static int sum;
static int ref;
};
int Sum::sum=0;
int Sum::ref=1;
class Solution {
public:
int Sum_Solution(int n) {
Sum arr[n];//变长数组在VS中不能用
return Sum::GetRet();
}
};
在这段代码中我们可以看到Sum类和Solution类紧密相关,所以我们可以把sum类设置为Solution类的内部类。
class Solution {
private://设计Sum为Solution的内部类,并把它放在Solution类的私有里,Sum类变成Solution类的专属类。
class Sum
{
public:
Sum()
{
sum+=ref;
ref++;
}
static int GetRet()
{
return sum;
}
private:
static int sum;
static int ref;
};
public:
int Sum_Solution(int n) {
Sum arr[n];
return Sum::GetRet();
}
};
int Solution::Sum::sum=0;
int Solution::Sum::ref=1;
六.匿名对象
1.用类型(实参)定义出来的对象就是匿名对象(没有名字)。
2.匿名对象的生命周期只在当前一行,一般临时定义个对象当前用一下即可,就可以定义匿名对象。
class A
{
public:
A(int a = 2)
:a(3)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int a;
};
int main()
{
//编译器不会报错,但是不建议这么用,编译器会分不清是函数的声明,还是aa1的初始化
//A aa1();
A();//匿名对象
A(1);//匿名对象

6544

被折叠的 条评论
为什么被折叠?



