目录
一、再谈构造函数
1.构造函数体赋值
2. 初始化列表
3.explicit关键字
二、static成员
1.概念
2.特性
三、友元
1.友元函数
2.友元类
四、内部类
在构造对象时,我们通过构造函数来初始化类中的成员变量。下面是一段构造函数:
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数完成了对成员变量的赋值。在构造函数的函数体中进行赋值其实并不能被称为初始化,因为在函数体中可以进行多次赋值,如下代码:
Date(int year, int month, int day)
{
_year = 2000;
_year = year;
_month = month;
_day = day;
_month = 12;
}
为了解决上述问题,C++的构造函数中引入了初始化列表。构造函数通过初始化列表来实现成员变量真正的初始化。在初始化列表中每个成员变量只能出现一次。
初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个" 成员变量 "后面跟一个 放在括号中的初始值或表达式 。
这是一段初始化列表的代码:
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意:
首先看如下的图片:
虽然在构造函数体中进行了赋值,但是编译器依然报错。正确的格式应该是使用初始化列表,如下代码:
class Date
{
public:
//初始化列表可以认为就是对象成员变量定义的地方
Date(int year, int n, int ref)
:_n(n)
,_ref(ref)
{
_year = year;
}
private:
//定义时不强求初始化,后面可再赋值修改
int _year; //声明
//只能在定义时初始化
const int _n;
int& _ref;
};
自定义类型同理,如下代码是没有默认构造函数的类:
class A
{
public:
//A的构造函数需要传参所以不是默认构造函数
A(int a)
{
_a = a;
}
private:
int _a;
};
class Date
{
public:
//初始化列表可以认为就是对象成员变量定义的地方
Date(int year, int n, int aa)
:_n(n)
//没有默认构造函数,所以要在初始化列表中初始化
,_aa(aa)
{
_year = year;
}
private:
int _year;
const int _n;
A _aa;
};
如果给上文代码A的构造函数加上缺省值,就不用初始化A类的成员变量
我们看下面这道题目:
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,_a2用_a1来完成初始化,这是的_a1还是随机值,所以_a2会是随机值,之后再用a来初始化_a1值为1,最终得到答案为。
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
};
int main()
{
Date d1(2022); //构造
//隐式类型转换
Date d2 = 2022; //构造 + 拷贝构造 ——> 优化成构造
}
//Date d3(d1); //拷贝构造
//Date d4 = d1;//拷贝构造
对于Date d2 = 2022就发生了类型转换。这里编译器会先用2022来构造一个Date类型的临时对象,再用这个临时变量来拷贝构造d2这个变量。但是编译器把d2构造的过程进行优化,构造+拷贝构造会被优化为直接用2022来构造d2。
如果想要让编译器不发生隐式类型转换,可以在函数前加上explicit关键字,这时编译器就不允许在构造时发生隐式类型转换。(会发生如下报错)
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化
class Date
{
private:
int year;
int month;
int day;
static int tmp;
};
int main()
{
cout << sizeof(Date) << endl; //12
}
这里运行的代码结果为12。这里Date类的大小是根据机构体大小的规则来计算的,可见static变量并没有被计算在其中。而是被和全局变量一起放在了静态区。
class Date
{
private:
//声明
static int A;
static int B;
};
//定义
int Date::A = 10;
int Date::B = 20;
class A
{
public:
static void func()
{
cout << K << endl; //静态成员函数可以访问静态变量
cout << A << endl; //err,此函数没有this指针无法访问
}
private:
//声明
static int K;
int A;
};
//定义
int A::K = 10;
当静态成员为公有时,可有如下三种方式:
class A
{
public:
//声明
static int K;
};
//定义
int A::K = 10;
int main()
{
A a;
cout << a.K << endl; //通过对象.静态成员来访问
cout << A::K << endl; //通过类名.静态成员来访问
cout << A().K << endl; //通过匿名对象.静态成员来访问
return 0;
}
友元函数一般被用来解决运算符重载的问题。
假如要重载cin的>>和cout的<<,我们首先想到的是在类内进行函数重载。我们用cout来举例:因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。(如下代码)
class Date
{
public:
Date(int year = 2000, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& out)
{
out << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2002, 3, 4);
d1 << cout;//由于成员函数第一个形参是隐藏的this指针,所以如上写法d1要放在<<的前面
cout << d1;//err
return 0;
}
为了解决这个问题就要用到友元类:
通过友元类,函数可以定义类外,但是同时可以访问类中的private变量,在通过如下代码来实现:
class Date
{
//友元函数的声明
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
Date(int year = 2000, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
//友元函数的实现
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(2002, 3, 4);
cin >> d1; //d1在操作符左边
cout << d1;
return 0;
}
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。例:假如有Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递。例:如果C是B的友元, B是A的友元,则不能说明C时A的友元。
友元关系不能继承。
概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类 。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。 外部类对内部类没有任何优越的访问权限 。
特性:
内部类可以定义在外部类的public、protected、private都是可以的。 注意内部类 可以直接访问外部类中的static成员 ,不需要外部类的对象/类名。 sizeof(外部类)=外部类 ,和内部类没有任何关系。
class A
{
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}