初始化列表
先看两种不好的初始化
1、b(缺省值)只能初始化为1,aa只能为0
class A
{
public:
A(int a = 0)//不写=0会显示出错为B没有合适的构造函数可以用,因为B要调用A的默认构造
{
_a = a;
}
}
private:
int _a;
};
class B
{
public:
private:
int _b = 1; // 内置类型--默认构造函数不能让内置类型初始化
A _aa; // 自定义类型
};
int main()
{
B b;
return 0;
}
2、显示的初始化–构造函数体内初始化
class A
{
public:
A(int a = 0)//调用构造
{
_a = a;
cout << "A(int a = 0)" << endl;
}
A& operator=(const A& aa)//调用赋值
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
private:
int _a;
};
class B
{
public:
B(int a, int b)
{
//有名对象
//A aa(a);
//_aa = aa;--赋值的想法
//匿名对象--作用域只在当前函数
_aa = A(a);
_b = b;
//_aa.a=a--err,成员变量为私有的不能随便访问或者是修改
}
private:
int _b = 1; //内置类型--这里给的是缺省值,这里不是定义
A _aa; //自定义类型
};
int main()
{
B b(10, 20);
return 0;
}
自定义类型显示初始化的过程调用了哪些函数
3、初始化列表–重点
class A
{
public:
A(int a = 0)
{
_a = a;
cout << "A(int a = 0)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
private:
int _a;
};
class B
{
public:
B(int a, int b)
//函数体内初始化 -> 自定义类型成员,改用初始化列表初始化,可以提高效率
:_aa(a)//直接调用它的构造
{
_b = b;
}
private:
int _b = 1; // 内置类型
A _aa; // 自定义类型
};
int main()
{
B b(10, 20);
return 0;
}
4、尽量使用初始化列表初始化,有哪些是必须使用初始化列表
- const类型的成员必须在定义的时候初始化–不能用构造函数体内赋值,要用初始化列表初始化
- 引用也必须在初始化定义–用初始化列表初始化
- 无默认的初始化构造函数的自定义类型成员-a–用初始化列表初始化
class A
{
public:
A(int a)
{
_a = a;
cout << "A(int a = 0)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
private:
int _a;//无默认的构造函数
};
class B
{
public:
//初始化列表即成员变量定义的地方
B(int c, int& ref, int a)//这里的ref为main函数的ref别名,为了让下面_ref改变能传回main函数而用引用,不用引用则引用的是局部变量即非法空间,调用完构造函数栈帧就销毁了,main函数会因为调用非法空间而ref返回随机值
:_c(c)
, _ref(ref)
, _aa(a)
{//初始化列表可以和函数体内初始化
//_c = c;--err
//_ref = ref;--err
_ref = 100;//在函数里面改变_ref,ref也改变,因为_ref是上面ref的别名
}
private:
const int _c;//const
int& _ref;//引用
A _aa;//无默认构造函数的自定义类型
};
int main()
{
int ref = 10;
B b(1, ref, 1000);
cout << ref << endl;
return 0;
}
5、初始化列表和函数体内初始化可以混合着用
6、成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中出现的先后次序无关
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();
}
- 输出 a1=1,a2=随机值
- 先用-a1(此时为随机值)初始化-a2,则-a2为随机值
- 再用a(值为1)来初始化-a1,则-a1为1
- 所以列表中初始化顺序和声明的顺序最好保持一致
单参数–两种方式调用都可以
void TestDate()
{
Date d1(2018);
d2 = 2019;//隐式转换--通过拷贝构造
}
- 两行虽然结果一样的,但是直接调用构造函数,但是对于编译器而言过程是不一样的,第二行是优化后的结果
让程序计算,下面程序构造了多少个对象 (构造+拷贝构造)
用countC和countCC来计算,不能使用count,因为std是展开的会出现命名空间的问题
int countC = 0;
int countCC = 0;
class A
{
public:
A()//构造
{
++countC;
}
A(const A& a)//拷贝构造
{
++countCC;
}
};
A f(A a)
{
A ret(a);
return ret;
}
int main()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
cout << countC << endl;
cout << countCC << endl;
return 0;
}
static的作用
1、上面算count的程序可以在main函数里countC=1就能更改countC的值,所以要使用static
class A
{
public:
A()
{
++_count;
}
A(const A& a)
{
++_count;
}
private:
int _a;// 存在定义出的对象中,属于某个对象
//跟全局变量比较,他受类域和访问限定符限制,更好体现封装,别人不能轻易修改它
static int _count;//存在静态区,属于整个类,也属于每个定义出来的对象共享 --声明
};
//定义初始化
// 静态成员变量不能在构造函数初始化,在全局位置定义初始化--不在这写会报错
int A::_count = 0;
int main()
{
cout << sizeof(A) << endl;//这里算的是A中定义出来的对象的大小
//public情况下才能输出下面--属于整个类,也属于每个定义出来的对象共享
//A::_count = 10;//因为封装了所以在这里就不能修改了--err
//cout << A::_count << endl;
//cout << a1._count << endl;
//cout << a2._count << endl;
}
- 只算_a的值,不算static的
2、私有的成员不能访问–写一个公有的构造函数来访问
class A
{
public:
A()
{
++_count;
}
A(const A& a)
{
++_count;
}
// 静态成员函数没有this指针
static int GetCount()
{
//_a = 1;--err因为它没有this指针
return _count;
}
private:
int _a; //sizeof(A) 算的是它
static int _count;//静态变量不能再这里给缺省值,因为静态成员不在构造函数初始化,要在类外面全局位置初始化
// 声明
};
int A::_count = 0;
A f(A a)
{
A ret(a);
return ret;
}
void test()
{
A a1 = f(A());
A a2;
A a3;
a3 = f(a2);
}
int main()
{
test();
//1、有名对象
//A ret;
//cout << ret.GetCount() - 1<< endl;//-1是减去自己定义的
//2、匿名对象 --现在类放在test而不是放在main函数里所以只能用匿名对象
//cout << A().GetCount() - 1<< endl;
//3、使用静态成员函数--不需要使用对象来调用,通过类域就可以了
cout << A::GetCount() << endl;
//cout << A().GetCount()-1 << endl;//静态函数也可以通过对象来访问
return 0;
}
static的作用–重点
- c
- 1、修饰全局变量和全局函数,改变链接属性,只在当前文件可见
- 2、修饰局部变量,改变声明周期
- 上面的特性在C++中依旧有用,C++兼容c的这些特性
- cpp
- 1、修饰成员变量和成员函数,成员变量属于整个类,所有对象共享,成员函数没有this指针
友元函数
1、破坏了封装,能不用就别用
2、友元函数不能用const修饰
3、普通函数、静态成员函数–不能使用const修饰,只有非静态的成员函数 才能用const修饰,因为const修饰的其实是this指针指向的对象
class Date
{
// 友元函数--C++在类外不能访问private需要写公有构造函数,但友元函数可以提供访问private的权力
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 0, int month = 0, int day = 1)//重载初始化
: _year(year)
, _month(month)
, _day(day)
{}
//不能写成成员函数
/*void operator<<(ostream& out)
{
out << _year << "/" << _month << "/" << _day << endl;
}*/
private:
int _year;
int _month;
int _day;
};
// 为了让cout在第一个参数,左操作数,我们就只能写成全局的
// 其次就是operator<<搞成友元,可以在类中访问私有。
// 但是operator<<(流插入)不是必须友元,还有其他方式
ostream& operator<<(ostream& out, const Date& d)//要有返回值和用引用返回是因为考虑连续输出情况
{
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
int main()
{
Date d1, d2, d3;
//d1.operator<<(cout);
//d1 << cout;
// 运算符重载,运算符有几个操作数,重载函数就有几个参数,左操作数是第一个参数,右操作数是第二个参数
// operator<<写成成员函数,this指针默认占据了第一个位置,对象(d1)就要做左操作数(但是我们需要cout做左操作数)
// 那么用起来就不符合流(对象)特性,虽然可以用,但是不符合运算符原来的用法和特性--所以不能写成成员函数得放到类外面
cin >> d1 >> d2;
cout << d1 << d2;
return 0;
}
2、一个函数可以是多个类的友元函数
// 前置声明
class B;
class A
{
friend void f(const A& aa, const B& bb);
public:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
class B
{
friend void f(const A& aa, const B& bb);
public:
//B()
// :_p(nullptr)
// , _aa(100)
//{}
private:
// 给的缺省值,这里不是定义,
// 这里只是声明,所以这里不是初始化
int _b = 0;
int* _p = (int*)malloc(sizeof(int) * 10);
//A _aa = A(10);先构造再拷贝构造
A _aa = 10;
// 静态变量不能在这里给缺省值,因为静态成员不在构造函数初始化,要在类外面全局位置定义初始化
//static int _n = 10;--err
};
void f(const A& aa, const B& bb)
{
cout << aa._a << endl;
cout << bb._b << endl;
}
int main()
{
A aa;
B bb;
f(aa, bb);
return 0;
}
3、友元类
- 元关系是单向的,Date是Time的友元,在Date类中可以使用对象访问Time的私有保护成员,但是Time不是Date的友元,在Time类中不可以使用对象访问Date的私有保护成员
- 友元不具有传递性
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
return 0;
}
内部类–在类中定义类
- 在类A中写类B,B天生就是A的友元,则B可以直接访问A的成员
- sizeof(外部类)–只算外部类的大小与内部类无关
class A
{
private:
//声明
static int k;
int h;
public:
class B // B天生就是A的友元,则B可以直接访问A的成员
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
private:
int _b;
};
};
//定义
int A::k = 0;
int main()
{
cout << sizeof(A) << endl;
// 这里计算A类型对象大小的时候,不考虑。因为B作为A的内部类,跟普通类并没有什么区别,
// 只是定义在A的内部,他受到A的类域的限制和访问限定符的限制
A aa;
A::B bb;
//B直接访问A的成员
bb.foo(aa);
return 0;
}