给对象的所有内部的成员变量初始化
以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。
类中包含以下成员时,必须放在初始化列表位置进行初始化:引用成员变量、const成员变量、自定义类型成员(且该类没有默认构造函数时)。这些成员无法在构造函数中被赋值。
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
问:输出结果是什么?
A. 输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值
答案:D
分析:类中两变量的声明顺序是先_a2再_a1,因此在初始化列表中先执行_a2(_a1),将_a2赋值为随机值(因为此时的_a1是随机值),再执行_a1(a),将_a1赋值为1。最终打印的顺序是先_a1再_a2,因此输出“1 随机值”。
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
构造函数体赋值本质上不是初始化,初始化列表是初始化:
初始化只能进行一次,但在函数体内可以实现对一个变量多次赋值,因此构造函数体内赋值不是初始化;
初始化列表要求每个成员变量在列表中只能出现一次,就是在保证初始化只能进行一次,因此初始化列表是初始化。
对于内置类型,可以进行类型转换,如double d = 1语句就是将整型1隐式转换为double类型。同样的,也可以将内置类型隐式转换成自定义类型。
对任何编译器而言,转换成的这个类对象的构造函数只有单个参数,或者除第一个参数无默认值其余均有默认值。
构造函数只有单个参数的情况:
Date(int year)
:_year(year)
{}
除第一个参数无默认值其余均有默认值的情况:
Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
内置类型转自定义类型是由构造函数支持的,构造函数中的参数是什么类型,就支持什么类型的数据转成类类型。
构造函数以一个整型作为参数;
内部过程:
①用3构造一个临时对象
②用这个临时对象拷贝构造对象aa3
(和例1一样,构造函数以一个整型作为参数)
内部过程:
①先用3构造一个临时对象
②ra就是这个临时对象的别名
注:引用起别名时,需要注意权限对等(可平移和缩小,不可放大)。隐式转换成引用类型的话需要在引用前加上const,因为本质上是将一个临时变量转换成引用,临时变量具有常属性,需要用const修饰的变量来接收临时变量(权限平移)
(和例1例2一样,构造函数以一个整型作为参数)
内部过程:(隐式转换了两次)
①先将double类型的3.33隐式转换成int类型的3
②再用3构造一个临时对象
③用这个临时对象拷贝构造对象aa4
注意:和整型->类类型不同:整型->类类型需要的构造函数是以整型作为参数的;而指针->类类型需要以指针类型的变量作为该构造函数的参数
构造函数以一个整型指针作为参数;
如果在有explicit关键字的情况下,非要进行类型转换,只能强制转换:
声明为static的类成员(包含成员变量和成员函数)称为类的静态成员;
用static修饰的成员变量称为静态成员变量;
用static修饰的成员函数称为静态成员函数。
对类内静态成员变量的访问:
可见二者访问的是同一个东西,同一块地址。
借助类对象在生成和销毁时自动调用构造函数(或拷贝构造函数)、析构函数的特性,可以定义一个全局变量用来计数,每调用一次构造函数或析构函数,就+1,每调用一次析构函数就-1。
因为库里面count是一个函数,命名冲突。
解决命名冲突需要注释掉“using namespace std”,但这样后序比较麻烦,如果想使用库里的东西的话需要加限定符“std::”。
这样每新生成一个对象,都会新生成一个count,count的初始值都是0,无法起到计数作用。
外部可以随意访问这个新命名空间内的变量,可能导致变量被恶意修改从而无法得到正确的值。
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;
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}
①由于变量是静态的,被所有对象共同拥有,从而可以累加计数;
②变量被private修饰,外界无法直接得到,从而保证结果不被恶意篡改;
③外部想要得到变量计算出来的值,需要一个public函数;
④用函数得到这个累加值,因此该函数也要是静态的才能知道类加值是多少;
⑤静态成员函数内无this指针,只能通过返回值的方式得到值。
只能通过这个静态函数得到值,不可以通过其修改,进一步保证了封装性和数据的准确。
链接: https://www.nowcoder.com/share/jump/7711188001706953836169
要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句
定义一个类类型的数组,数组有多少个元素就要创建多少个类对象,借助每创建一个对象自动调用构造函数特性,实现求和。需要定义两个静态变量_i和_ret,_i用来自动加一,_ret用来将所有_i加和。
class Solution {
public:
int Sum_Solution(int n) {
}
};
#include
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];
return sum::GetRet() ;
}
};
友元提供了一种突破封装的方式,有时提供了便利。
但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
在类的内部声明,声明时需要加friend关键字;在类外部定义。
友元函数可以直接访问类的私有成员。
重载流插入操作符函数operator<<,符号<<左边必须是cout,右边是自定义的对象,因此为了参数顺序正确,只能将函数定义在类的外部(定义在类内的话,默认第一个参数为this指针,使得参数顺序错误),然而这个重载的函数需要用到类对象的私有成员变量,因此需要友元这种突破封装的方式,使得类外函数可以自由使用类内成员变量。
class Date
{
//类内部声明
friend ostream& operator<<(ostream& _cout, const Date& d);
public:
Date(int year = 1900, int month = 1, int day = 1)
: _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;
}
int main()
{
Date d;
cout << d << endl;
return 0;
}
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。A是B的友元类,A可以自由访问B类的所有成员变量,但B不可以访问A。
友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。
如果一个类A定义在另一个类B的内部,类A就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
证明:类B定义在类A内部,两个类各自有一个整型的成员变量,计算此时B类型的大小:
class A
{
private:
int a;
public:
class B
{
public:
int b;
};
};
int main() {
cout << sizeof(A) << endl;
return 0;
}
类B定义在类A外部,在类A内部有一个B类型的成员变量,计算类A的大小:
class B
{
public:
int b;
};
class A
{
int a;
B b;
};
int main() {
cout << sizeof(A) << endl;
return 0;
}
说明计算类A的时候会计算上B,类B的大小是类A的大小的一部分。
在最初版本的基础上进行了如下几方面的优化:
class Solution {
class sum
{
public:
sum(){
ret+=i++;
}
};
public:
int Sum_Solution(int n) {
sum arr[n];
return ret;
}
private:
static int i;
static int ret;
};
int Solution::i=1;
int Solution::ret=0;
(aa2和aa3分别代表拷贝构造的两种写法)
A aa1(1);
A aa2(aa1);
A aa3=aa1;
A aa1(1);
A aa2(2);
aa1=aa2;
在同一个表达式中,先后出现两次构造函数(或拷贝构造)时,编译器会将两函数合并成为一个函数,即两个步骤合并为一步。
现在有一个类A,其中有1个整型成员变量,public部分显式写出来4个函数,依次是构造、拷贝构造、=运算符重载函数、析构函数:
(增加了打印的语句,方便得知编译器内部到底用了哪些函数及函数调用的过程)
class A
{
public:
A(int a) {
_a = a;
cout << "A(int a)" << endl;
}
A(const A& aa) {
_a = aa._a;
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa) {
_a = aa._a;
cout << "A& operator=(const A& aa)" << endl;
return *this;
}
~A() {
cout << "~A()" << endl;
}
private:
int _a = 1;
};
基于类A展示以下优化案例
int main() {
A aa1 = 1;
}
构造+拷贝构造->构造,最终会被优化为一个构造函数,即直接用1构造对象aa1,省去了构造临时对象和销毁临时对象的过程
int main() {
const A& aa2 = 1;
}
void func1(A aa) {
}
int main() {
A aa3(1);
func1(aa3);
}
void func1(A aa) {
}
int main() {
A aa3(1);
func1(aa3);
func1(A(1));
}
构造函数+拷贝构造函数->构造函数。将原本的第4步过程中先后出现的构造函数+拷贝构造函数,优化成1个构造函数;对应的,将原本的第5步过程中的两个析构函数优化的只剩1个析构函数(构造函数从2个变成1个了,则析构肯定要跟着变)。
A func2() {
A aa(1);
return aa;
}
int main() {
A aa4(2);
aa4 = func2();
return 0;
}
A func2() {
A aa(1);
return aa;
}
int main() {
A aa5=func2();
func2();
return 0;
}
对于语句1:A aa5=func2();
对于语句2:func2();
拷贝构造+拷贝构造=拷贝构造。对于语句1:A aa5=func2();,会将3和4两个连续的拷贝构造函数合并成1个拷贝构造函数,相当于跳过了拷贝构造临时对象的过程,因此也不需要析构临时对象