一、初始化列表
- 对象的成员变量除了可以在构造函数内部完成初始化之外,还可以在构造函数名和{}之间完成初始化,在这里完成初始化要写初始化列表;
- 初始化列表的写法:以冒号开头,后面接上属性名,每一个属性名后面加上一个(),括号里面写上一个值或者一个表达式,括号里面写的就是这个属性的要被初始化的值;
- 若是每个成员变量有初始化的值,那么就正常初始化,若是没有在初始化列表里面写内容:先去看属性声明的地方有没有缺省值,有就利用缺省值进行初始化;没有的话,对于内置类型就初始化为随机值,对于自定义类型的就去调用它的默认构造函数进行初始化,若是没有默认构造函数。就编译出错;
- 初始化列表就是成员变量进行初始化定义的地方,每一个成员变量都要走一遍初始化列表;
- 对于const修饰的成员变量、引用类型的成员变量以及没有默认构造函数的自定义类型的成员变量一定要在初始化列表里面进行初始化;对于const修饰的成员变量,声明和定义不能分开,一旦声明就是定义了,没有给值就是随机值,这个随机值不会被改变,就是这个const修饰的成员变量的值,所以一定要在初始化列表里面进行初始化(初始化就是成员变量进行初始化定义的地方),否则,这个成员变量的值就一直是固定的随机值;对于引用类型,它要求声明时必须给初始值,否则编译出错,所以在初始化列表里面要写;对于没有默认构造函数的自定义类型,不写在初始化列表里面,就相当于没有初始化(没有默认构造函数可以调用),所以一定要写在初始化列表里面;
- 初始化的顺序先后是看声明的顺序先后,而不是看初始化列表里面写的成员变量的先后。
//初始化列表
//1、初始化了,正常初始化
//2、没有初始化
//a、调用缺省值去初始化
//b、没有缺省值:
// x、对于内置类型,初始化的值随机
// y、对于没有默认构造的自定义类型,编译出错
//3、没有默认构造的自定义类型、const修饰的类型、引用类型必须在初始化列表初始化
###代码示例1:
class Date
{
public:
Date(int year=1,int month=1,int day=1)
:_year(year),_month(month),_day(day)//或者:_year(1),_month(1),_day(1)
{}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
int i = 0;
Date d1(i);
d1.Print();
return 0;
}
若是初始化列表里面成员变量后面的括号里面就是一个具体的值,那么就用这个值;若是初始化列表里面写这个成员变量,那么就调用成员变量声明处的缺省值;括号里面写的是形参那么就用形参的默认值进行初始化:最优先的是利用括号里面的进行初始化,接着是看形参处的默认值(初始化里面写了这个成员变量的情况);看成员变量声明处有没有缺省值(初始化列表里里面没有写这个成员变量的请款)
初始话列表后面的花括号里面也可以写代码,当成员变量有申请资源时,可以再{}里面检查开辟是否成功。
###示例代码2:
class Time
{
public:
Time(int t)//非默认的构造函数
{
_t = t;
}
private:
int _t;
};
class Date
{
public:
Date(int xx)
:_year(1222),_month(10),_day(18),_x(xx),_n(100),_time(200)
{}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//这里不是初始化,某一个成员变量没有写在初始化列表里面时,就利用这里的值去初始化
int _year=20;
int _month=20;
int _day=20;
int& _x;//或者直接const int& x=1;
const int _n=10;
Time _time=20;
};
int main()
{
int i = 0;
Date d1(i);
d1.Print();
return 0;
}
只有初始化列表里面不写时,才会调用缺省值,也就是private里面的;
对于引用类型,不能直接在声明处给缺省值,除非它是const修饰的(临时变量具有常性),所以在实例化对象时,给一个值 (i) ,让Date的形参接收(xx),再给这个引用成员变量进行初始化(_x(xx) )
二、类型转换
C++支持内置类型隐式转换成类类型,条件是需要内置类型为参数进行对象的初始化;当构造函数前加上explicit时,就不再支持隐式类型转化;
###代码示例:
class A
{
public:
A(int a1)//写成构造函数也可以
:_a1(a1)
{}
void Print()
{
cout << _a1 << endl;
}
private:
int _a1 = 1;
};
int main()
{
A x = 10;
x.Print();
return 0;
}
A x = 10;既不是拷贝构造也不是赋值重载,而是隐式类型转化了,10首先作为一个实参初始化一个对象,之后再让这个临时对象对x进行拷贝构造,所以x里面的属性是10;
若是在初始化的函数前面加上explicit:
可以同时对含有多个属性的对象进行隐式类型转化,要用 {}
###代码示例:
class A
{
public:
A(int a1,int a2)
:_a1(a1),_a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 1;
int _a2 = 2;
};
int main()
{
A x = {10,20};
x.Print();
return 0;
}
同样地,A前面加上ecplicit也不能转化了。
三、static成员
- 用static修饰的成员变量称作静态成员变量,存放于静态区中,为一个类的所有对象所共有;
- 静态成员虽不是存放于对象中,但是要访问它也受到访问限定符的限制;
- 静态成员不能给缺省值,因为实际不在对象中,所以静态成员不会走构造函数初始化列表,它的初始化只能在类外部初始化;
- 在成员函数前面加上static就是静态成员函数,静态成员函数没有this指针,所以它不能访问非静态的成员变量,可以访问静态成员变量;非静态成员函数无论是静态还是非静态成员变量都可以访问;
- 静态成员函数也存放于静态区,要调用静态成员函数可以通过直接在类于查找(::),也可以通过对象调用;但是非静态成员函数是放在对象里面的,只能通过对象调用。
###代码示例1:
class A
{
public:
A()
{
_count++;
}
A(const A& x)
{
_count++;
}
~A()
{
_count--;
}
static int Get_count()
{
return _count;
}
private:
static int _count;
};
int A::_count = 0;//静态成员的初始化
int main()
{
A a1;
cout << A::Get_count() << endl;
A a2(a1);
cout << a2.Get_count() << endl;
cout << a2.Get_count() << endl;
}
写一个类,里面有静态成员变量,专门用来记录每次初始化对象的数量:静态成员变量是一个类的所有对象共有的,所以可以每次初始化一个对象后就让这个静态成员加1,因为在静态区,所以下次初始化对象它的值不会被刷新,这样就可以来记录一个类实例化对象的个数;
初始化要指定类域,在类的外部;
结果显而易见,无论是相同类哪个对象,它们的静态成员变量都是一样的。
###示例代码2:
class A
{
public:
A()
{
_count++;
}
A(const A& x)
{
_count++;
}
~A()
{
_count--;
}
static int Get_count()//不含this指针,函数内部只能访问静态成员
{
return _count;
}
void Print()//含this指针,无论成员变量静态与否都可以访问
{
cout << _a << " " << _b << " " << _count << " " << endl;
}
private:
static int _count;
int _a = 1;
int _b = 1;
};
int A::_count = 0;//静态成员的初始化
int main()
{
A a1;
cout << A::Get_count() << endl;
a1.Print();
}
Print成员函数是非静态的,无论成员变量静态与否,函数内部都可以使用;
但是对于静态成员函数内部却只能访问静态成员变量:
表明函数形参没有this指针,就是没有特定对象 。
四、有元
- 友元函数提供了一种突破类的方式,通过在一个类里面声明一个友元函数的方式,这个友元函数就可以访问这个类的私有成员变量了;
- 友元函数可以声明在一个类的任何地方,不受访问限定符的限制;友元函数不是成员函数,只是在类里面声明了而已;
- 一个函数可以是多个类的友元函数,这个函数可以访问多个类的成员变量;
- 一个类也可以在另一个类中友元声明,这样,这个类就可以访问另一个类的成员变量了;
- 友元关系是单向的,A在BB中友元声明,只能说明A是B的友元,而B不是A的友元,A可以访问B的成员变量,但是B不能访问A的成员变量;友元关系没有传递性,A是B的友元,B是C的友元,但是不能说明A是C的友元。
###代码示例1:
class B;
class A
{
friend void func(const A& x, const B& y);
public:
A()
{}
private:
int _a1=1;
int _a2=2;
};
class B
{
public:
B()
{}
private:
friend void func(const A& x, const B& y);
int _b1 = 3;
int _b2 = 4;
};
void func(const A& x, const B& y)
{
cout << x._a1 << endl;
cout << x._a2 << endl;
cout << y._b1 << endl;
cout << y._b2 << endl;
}
int main()
{
A x;
B y;
func(x, y);
return 0;
}
首先在第一行声明类类型B:程序寻找都是向上查找,A中友元函数形参B先向上找,若是没有成名,会报错;
这里func同时是两个类的友元,可以访问两个类中的成员变量;
###代码示例2:
class A
{
friend class B;
public:
A()
{}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B()
{}
void func_B(const A& x)
{
cout << x._a1 << endl;
cout << x._a2 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};
int main()
{
A x;
B y;
y.func_B(x);
}
想要让一个类中的成员函数访问另一个类的私有成员,就要让这个类成为另一个类的友元类;
五、内部类
- 内部类是在一个类里面写一个类;内部的类和外部的类相互独立,外部类实例化出来的对象不能访问内部类,内部类只是受到外部类的类域限制和访问限定符的限制;
- 内部类默认是外部类的友元类;
- 内部类本质上是一种封装;若是一个类A设计出来就是要给类B使用的,那么可以考虑把A设计成B的内部类;若是放到private/protected的位置那么A就是B的专属内部类。
###代码示例:
//内部类
class A
{
public:
class B
{
public:
B()
{}
void Print(const A& x)
{
cout << _c << " " << _d << endl;
cout << _a << " " <<x._b << endl;//A中的静态成员随便访问,但是非静态成员要通过A的对象访问
}
private:
int _c = 3;
int _d = 4;
};
private:
static int _a ;
int _b = 2;
};
int A::_a = 1;
int main()
{
cout << sizeof(A) << endl;
A x;
A::B y;//类域限制
y.Print(x);
}
B独立于A,那么A的大小只与自己的成员变量有关 ,A的成员变量只有一个静态的整形和一个整形,静态的存放于静态区,不在对象中,所以A的大小只是一个整形的大小;
要用B实例化对象,先要在类A中找到B,B是A的友元类,传参A的对象,可以通过这个对象去访问A的成员变量。
六、匿名对象
匿名对象是类类型加上();括号;内部可以写要初始化的值;代码运行到匿名对象这一行,这种对象开始创建,走完这一行,匿名对象就析构结束;匿名对象主要是用在只想调用类里面的函数的场景
###代码示例:
class A
{
public:
A(int a=1)
:_a(a)
{
cout << "A()" << endl;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << _a << endl;
}
private:
int _a = 1;
};
int main()
{
A();//匿名对象
A(4).Print();
}
//运行结果
A()
~A()
A()
4
~A()
可以看到每次初始化匿名对象就会调用构造函数初始化列表,但是这一行走完就会析构这个对象。