在创建对象时,我们知道编译器会通过调用构造函数,给对象中各个成员变量一个合适的初始值。
Date(int year= 1984, int month = 5, int day =20)
{
assert(year >= 0 &&
month >= 1 && month <= 12
&& day >=1
&& day <= GetMonthDay(year,month) );
_year = year;
_month = month;
_day = day;
}
虽然上述构造函数调用之后,对象已经有了一个初始值,但是不能将其称作类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值!
语法格式: 以一个冒号开始,接着是一个以逗号分割的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
Date::Date(int year= 1984, int month = 5, int day =20)
:_year(year)
,_month(month)
,_day(day)
{}
这就是最基本的初始化列表。
注意:每个成员变量在初始化列表中只能出现一次!
初始化列表的作用?
** 初始化列表是成员变量定义的地方**
例如 引用类型的变量,const类型的变量等,必须通过初始化列表定义!
class A
{
public:
A(int n,int ret)
:_n(n)
,_ret(ret)
{
//const变量与引用只能通过初始化列表进行初始化,也就是定义
}
private:
const int _n;
int& _ret;
};
class B
{
public:
B(int b)
{
_b = b;
}
private:
int _b;
};
class A
{
public:
A(int n,int ret,int x)
:_n(n)
,_ret(ret)
,_b(x)
{
}
private:
const int _n;
int& _ret;
B _b;
};
b没有默认构造函数,如果这时候不用初始化列表就会报错!
总结:尽量都在初始化列表初始化!
看一个题目:
经过下面的程序,_a1 和 _a2的值变为了什么:
A 1,1
B 程序崩溃
C编译不通过
D1,随机值
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
private:
int _a2;
int _a1;
};
int main()
{
A aa(1);
return 0;
}
答案:D
原因: 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,和它在初始化列表中过的先后次序无关!
class A
{
public:
A(int a)
:_a1(a)
{}
private:
int _a1;
};
int main()
{
A b = 1; //隐式类型的转换 左边的1会通过构造函数先变为一个对象,然后通过一个临时变量,通过拷贝构造给A (构造+拷贝构造)
//A& c = 1;//编译报错,因为临时变量具有常性,权限放大了
const A& c = 1; //编译通过!
return 0;
}
class A
{
public:
explicit A(int a)
:_a1(a)
{}
private:
int _a1;
};
int main()
{
A b = 1;
const A& c = 1;
return 0;
}
当构造函数被 explicit修饰时,就会禁止单参构造函数的隐式类型的转换,上述代码编译无法通过!
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化!
用static计算拷贝构造和默认构造调用了多少次
class A
{
public:
A()
{
_count1++;
}
A(const A& a)
{
_count2++;
}
static int GetCount1()
{
return _count1;
}
static int GetCount2()
{
return _count2;
}
private:
static int _count1;
static int _count2;
};
int A::_count1 = 0;
int A::_count2 = 0;
int main()
{
A a1;
A a2(a1);
A a3(a2);
cout << "默认构造函数调用:" << A::GetCount1() << endl;
cout << "拷贝构造函数调用:" << a1.GetCount2() << endl;
return 0;
}
1.静态成员为所有类对象所共享,不属于某个具体的实例。
2. 静态成员必须在类外定义,定义时不能添加static关键字。
3. 类静态成员即可用类名::静态成员 或者 对象.静态成员来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
5. 静态成员和类的普通成员一样,也有 public 、protected、private 3种访问级别,也可以具有返回值。
class A
{
public:
A()
{
_count1++;
}
A(const A& a)
{
_count2++;
}
static int GetCount1()
{
return _count1;
}
static int GetCount2()
{
return _count2;
}
private:
static int _count1;
static int _count2;
int _a;
};
int A::_count1 = 0;
int A::_count2 = 0;
int main()
{
A a1;
cout << sizeof(a1) << endl;
return 0;
}
static并不会占对象的空间,他属于所有对象,被所有对象共享,在静态区
友元分为:友元函数和友元类
(友元提供了一种突破封装的方式,有时候提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。)
问题的引入:我们尝试在日期类中去重载流插入和流提取操作符 << >> 但是该重载函数无法在类内部完成,为什么呢?
因为cout的输出流对象和隐含的this指针抢占了第一个参数的位置,而this指针默认是第一个参数也就是左操作数了,这样左操作数就不是cout了,所以要求把 operator<<重载成全局函数,但是这样的话又会导致类外没法访问成员,那么这里就需要友元来解决。
std::ostream& operator<<(std::ostream& out, const Date& d)
{ //out 是cout的别名
out << d._year << "-" << d._month << "-" << d._day;
return out;
}
std::istream& operator>>(std::istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
//在全局实现的>> 和 << 的重载函数
class Date
{
public:
//友元函数
// 流输入,流输出运算符重载
friend std::ostream& operator<<(std::ostream& out, const Date& d);
friend std::istream& operator>>(std::istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
//友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要类的内部声明 并加上friend关键字
1.友元函数可以访问类的私有成员和保护成员,但不是类的成员函数。
2. 友元函数不能用const修饰
3. 友元函数可以在类定义的任何地方声明,不受类访问限定符的限制
4. 一个函数可以是多个类的友元函数
5. 友元函数的调用与普通函数调用原理相同
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
注意:
- 友元关系是单向的,不具有交换性!
- 友元关系不能传递,B是A的友元,C是B的友元,不能说明C是A的友元。
对于第一点:
class A
{
friend B;
public:
A(int a = 10,int x =5)
:_a(a)
,_x(x)
{}
private:
int _a;
int _x;
};
class B
{
public:
void Print(const A& a)const
{
cout << a._a << a._x << endl;
}
};
B是A的友元类,B中成员可以直接访问A的成员,但是A中成员不能访问B的成员。
概念:如果一个类定义在另一个类的内部,这个被定义在另一个类内部的类就被叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象调用内部类。外部类对内部类没有任何优越的访问权限。
注意: 内部类就是外部类的友元类。但是外部类不是内部类的友元类!
特性: