1.再看构造函数
1.1构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date {
public:
Date(int year, int month, int day) {//构造函数
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造哦函数体中的语句只能将其称为赋初值,而不能称作初始化,因为初始化只能初始化一次,而构造函数体内可以多次赋值
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;
};
注意:
1.每个成员变量在初始化列表中只能出现一次(因为初始化只能初始化一次)
2.类中包含以下成员,必须放在初始化列表的位置进行初始化:
(1)引用成员变量
第二行代码报错,而第三行代码正常
(2)const成员变量
第一行代码报错,而第二行代码正常
(3)自定义类型成员(且该类型没有默认构造函数时)
class A {//该类没有默认构造函数
public:
A(int val){ //注:这个不叫默认构造函数(需要传参调用)
_val = val;
}
private:
int _val;
};
class B{
public:
B()
:_a(2021) //必须使用初始化列表对其进行初始化
{}
private:
A _a; //自定义类型成员(该类没有默认构造函数)
};
3.尽量使用初始化列表初始化,以内不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
class Time
{
public:
Time(int hour = 0)
{
_hour = hour;
}
private:
int _hour;
};
class Test
{
public:
// 使用初始化列表
Test(int hour)
:_t(12)// 调用一次Time类的构造函数
{}
private:
Time _t;
};
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
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和a2是什么值?
很多同学认为是 1和1,因为觉得是a把_a1初始化成1,然后_a1再把_a2初始化成1
但实际上是:
这并不与我们看到的初始化顺序有关,而是与定义成员变量的先后顺序有关,代码中先定义的_a2,所以先初始化_a2,又因为_a1还未被初始化,所以_a2为随机值,然后再定义_a1,再初始化_a1,此时将a = 1,并初始化_a1,所以_a1为1
1.3 explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值,其余均有默认值的构造函数,还具有类型转换的作用
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 0) //单个参数的构造函数
:_year(year)
{}
void Print()
{
cout << _year << endl;
}
private:
int _year;
};
int main()
{
Date d1 = 2021; //支持该操作
d1.Print();
return 0;
}
在c++中,代码Date d1 = 2023等价于:
Date tmp(2021); //先构造
Date d1(tmp); //再拷贝构造
隐式类型转换:
int a = 10;
double b = a; //隐式类型转换
上述代码实际上是先用一个double类型的临时变量接受a,然后再将临时变量拷贝给b
要是想禁止上述构造函数的隐式类型转换,就可以用到explicit来修饰构造函数,此时编译就不再通过。
注意,只能禁用构造函数的,例如int转换成double等不可以使用explicit修饰
static成员
概念
声明为static的类成员称为类的静态成员。用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
特性
一、静态成员为所以类对象所共享,不属于某个具体的对象
#include <iostream>
using namespace std;
class Test
{
private:
static int _n;
};
int main()
{
cout << sizeof(Test) << endl;
return 0;
}
因为静态成员_n是存储在静态区,属于整个类,是类的所有对象,所以计算类的大小时静态成员不计入其中。
二、静态成员变量必须在类外定义,定义时不添加static关键字
class Test
{
private:
static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
有同学会注意到,为什么类外能访问到类内的私有成员,这是一个例外,这里时不会受到访问限定符的限制,不然就没办法对静态成员进行定义和初始化了。
三、静态成员函数没有隐藏的this指针,不能访问任何非静态成员
class Test
{
public:
static void Fun()
{
cout << _a << endl; //error不能访问非静态成员
cout << _n << endl; //correct
}
private:
int _a; //非静态成员
static int _n; //静态成员
};
含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。
四、访问静态成员变量的方法
1.当静态成员变量为公有时,有以下几种访问方式:
#include <iostream>
using namespace std;
class Test
{
public:
static int _n; //公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
Test test;
cout << test._n << endl; //1.通过类对象突破类域进行访问
cout << Test()._n << endl; //3.通过匿名对象突破类域进行访问
cout << Test::_n << endl; //2.通过类名突破类域进行访问
return 0;
}
2.当静态成员变量为私有时,有以下几种访问方式:
#include <iostream>
using namespace std;
class Test
{
public:
static int GetN()
{
return _n;
}
private:
static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main()
{
Test test;
cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
cout << Test().GetN() << endl; //2.通过匿名对象调用成员函数进行访问
cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
return 0;
}
五、静态成员和类的普通成员一样,也有public、private和protected这三种访问级别
所以当静态成员变量设置为private时,尽管我们突破了类域,也不能对其进行访问。
两个问题:
1.静态成员函数可以调用非静态成员函数吗?
2.非静态成员函数可以调用静态成员函数吗?
1.不可以,非静态成员有this指针,可以调用,而静态成员没有this指针,所以不可以调用
2.可以,因为非静态与静态成员函数都在类中,且非静态成员有this指针,故可以调用
友元函数
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
友元函数说明:
1、友元函数可以访问类是私有和保护成员,但不是类的成员函数。
2、友元函数不能用const修饰。
3、友元函数可以在类定义的任何地方声明,不受访问限定符的限制。
4、一个函数可以是多个类的友元函数。
5、友元函数的调用与普通函数的调用原理相同。
class A
{
// 声明B是A的友元类
friend class B;
public:
A(int n = 0)
:_n(n)
{}
private:
int _n;
};
class B
{
public:
void Test(A& a)
{
// B类可以直接访问A类中的私有成员变量
cout << a._n << endl;
}
};
友元类说明:
1、友元关系是单向的,不具有交换性。
例如上述代码中,B是A的友元,所以在B类中可以直接访问A类的私有成员变量,但是在A类中不能访问B类中的私有成员变量。
2、友元关系不能传递。
如果A是B的友元,B是C的友元,不能推出A是C的友元。
内部类
概念
如果一个类定义在另一个类的内部,则这个类被称为内部类。
注意:
1.此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类。
2、外部类对内部类没有任何优越的访问权限。
3、内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成 员。但是外部类不是内部类的友元。
特性
1、内部类可以定义在外部类的public、private以及protected这三个区域中的任一区域。
2、内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3、外部类的大小与内部类的大小无关。
#include <iostream>
using namespace std;
class A //外部类
{
public:
class B //内部类
{
private:
int _b;
};
private:
int _a;
};
int main()
{
cout << sizeof(A) << endl; //外部类的大小
return 0;
}
外部类A的大小为4,与内部类的大小无关