1.构造函数
1.构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式时以一个冒号开始,接着是一个一个以逗号分隔的数据成员列表,每个"成员变量"后面跟着一个放在括号中的初始值或者是表达式。
2.每个成员变量再初始化列表中只能出现一次,语法理解上可以认为初始化列表是每个成员变量定义初始化的地方。
3.引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
3.C++11支持再成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用的。
4.尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否会初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
5.初始化列表中按照成员变量在类中声明顺序进行初始化,和成员在初始化列表出现的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
初始化列表总结:
无论是否显示写初始化列表,每个构造函数都有初始化列表;
无论是否在初始化列表显示初始化成员变量,每个成员变量都要走初始化列表初始化;
"成员变量走初始化列表的逻辑:1.显示在初始化列表初始化的成员变量就按这个值来初始化。
2.未显示在初始化列表的成员变量:2.1类中声明位置有缺省值,就按缺省值初始化
2.2类中声明没有缺省值:内置类型成员变量,可能初始化为随机值,也可能是0等,具体看编译器; 自定义类型成员变量,会去调用这个成员变量的默认构造函数; 引用成员变量/const成员变量/没有默认构造函数的成员必须在初始化列表显示初始化或者未显示初始化但是声明时有缺省值,否则会编译报错。
cpp
#include <iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date()
:_month(2)
{
cout << "Date()" << endl;
}
void Print() const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year = 1;
int _month = 1;
int _day = 1;
Time _t = 1;
const int _n = 1;
int* _ptr = (int*)malloc(12);
};
int main()
{
Date d1;
d1.Print();
return 0;
}
2.类型转换
1.C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
2.构造函数前面加explicit就不再支持隐式类型转换。
3.类类型的对象之间也可以隐士转换,需要相应的构造函数支持。
cpp
#include <iostream>
using namespace std;
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)
{}
int Get() const
{
return _a1 + _a2;
}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
private:
int _a1 = 2;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{ }
private:
int _b = 1;
};
int main()
{
//构造一个A的临时对象,再用这个临时对象拷贝构造aa3
//编译器遇到连续构造+拷贝构造->优化为直接构造
A aa1 = 1;
aa1.Print();
const A& aa2 = 1;
//C++11之后才支持多参数转化
A aa3 = { 2, 2 };
return 0;
}
3.static成员
1.用static修饰的成员变量是静态成员变量,静态成员变量一定要站在类外进行初始化
2.静态成员变量是所有类对象共享的,不属于某个具体的对象,不存在对象中,存放在静态区。
3.用static修饰的成员函数,是静态成员函数,静态成员函数没有this指针
4。非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
5.突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。
6.静态成员也是类的成员,收public、protected、private访问限制符的限制
7.静态成员变量不能再声明位置给缺省值初始化,因为缺省值是构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
cpp
#include <iostream>
using namespace std;
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;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
return 0;
}
4.友元函数和友元类
1.再函数声明或者类声明的前面加friend,并且放到一个类里面,这就让这个变量或者是函数和类做了好朋友
2.外部友元函数可以访问类的私有和保护成员,友元函数只是一种声明,他不是类的成员函数。
3.友元函数可以在类的任何位置声明,不受访问限制符的限制。
4.一个函数可以是多个类的友元函数。
5.友元的关系是单向的,不具有交换性,你的朋友是你的朋友,你朋友的朋友不一定是你的朋友
cpp
#include <iostream>
using namespace std;
class B;
class A
{
friend void func(const A& a, const B& b);
private:
int _a1 = 1;
int _a2 = 1;
};
class B
{
friend void func(const A& a, const B& b);
private:
int _b1 = 2;
int _b2 = 2;
};
void func(const A& a, const B& b)
{
cout << a._a1 << endl;
cout << b._b1 << endl;
}
int main()
{
A a;
B b;
func(a, b);
return 0;
}
5.内部类
1.如果一个类定义在另一个类里面,这个里面这个类就叫做内部类。内部类也是一个独立的类,它只是收外部类类域限制和访问限制符的限制,所以外部类定义的对象中不包含内部类对象。
2.内部类默认是外部类的友元类。
3.内部类本质也是一种封装,如果A和B紧密相连,如果把A放到private/protected位置,那么A就是B的专属内部类,其他地方都用不了。
cpp
#include <iostream>
using namespace std;
class B
{
private:
static int _b;
int _d = 1;
public:
class A
{
public:
void f(const B& b)
{
cout << _b << " " << b._d << endl;
}
int _a;
};
};
int B::_b = 1;
int main()
{
cout << sizeof(B::A) << endl;
B::A a;
B b;
a.f(b);
return 0;
}
6.匿名对象
1.用类型定义出来的对象叫做匿名对象,以前定义的 类型 对象名(实际参数)定义出来的是有名对象
cpp
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 1)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A a;
A aa(2);
A();
A(2);
return 0;
}
2.匿名对象生命周期只在当前一行