- 1.再谈构造函数
- 1.1构造函数体赋值
- 1.2初始化列表
- explicit关键字
- 2.static成员
- 2.1概念
- 2.2特性
- 3.C++11 的成员初始化新玩法
- 友元
- 友元函数
- 友元类
- 内部类
- 内部类概念
- 内部类特性
在创建对象时,编译器会通过调用构造函数,给对象中的各个成员变量一个合适的初始值:
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
虽然通过调用上述的构造函数后,对象中的每个成员变量都有了一个初始值,但是构造函数中的语句只能将其称作为赋初值,而不能称作为初始化。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。
接下来验证一下:
const int _N;//没初始化,报错 const int _N;// 正确
const 修饰的变量必须在定义前初始化。
class Date
{
public:
//error :左值指定const 对象
//这里报错说明到构造函数体内时,成员变量已经定义出来了。
Date(int year = 0, int month = 1, int day = 1)
{
_N = 10;//报错
_year = year;//第一次赋值
//_year=1000;//第二次赋值
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
const int _N;
};
1.2初始化列表
初始化列表 :以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。
class Date
{
public:
// 构造函数
Date(int year = 0, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
, _N(10)
{}
private:
int _year;
int _month;
int _day;
};
注意事项:
一、每个成员变量在初始化列表中只能出现一次
因为初始化只能进行一次,所以同一个成员变量在初始化列表中不能多次出现。
二、类中包含以下成员,必须放在初始化列表进行初始化:
1、引用成员变量
2、const成员变量
3、自定义类型成员(该类没有默认构造函数)
解析:第3
默认构造分别是无参,全缺省构造和编译器默认生成的构造,编译器会默认调用自定义类型的默认构造函数。如下代码:
class A
{
public:
A(int a)
{
_a = a;
}
private:
int _a;
};
class Date
{
public:
// 这里报错,编译器找不到_aa的默认构造,
// 因为 A(int a)不属于默认构造,编译器不会调用
Date(int year = 0, int month = 1, int day = 1)
:_N(10)
// ,_aa() 相当于编译器隐士调用了默认构造,所以报错
// ,_aa(year) 正确初始化
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
const int _N;
A _aa;
};
三、尽量使用初始化列表初始化
在某些场景下可以减少一次调用默认构造。如下:
class A
{
public:
A(int a = 0)
{
cout << "A(int a = 0)" << endl;
_a = a;
}
A(const A& aa)
{
cout << "A(const A& aa)" << endl;
_a = aa._a;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
_a = aa._a;
return *this;
}
private:
int _a;
};
class Date
{
public:
// 不使用初始化列表
//Date(int year, int month, int day, const A& aa)
// ,_aa() //隐藏的调用默认构造 第二次
//{
// _aa = aa; //调用 A& operator=(const A& aa)
// _year = year;
// _month = month;
// _day = day;
//}
// 使用初始化列表
Date(int year, int month, int day, const A& aa)
:_aa(aa) // 调用 A(const A& aa)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year; // 声明
int _month;
int _day;
A _aa;
};
int main()
{
A aa(10);//调用默认构造 A(int a = 0) 第一次
Date d1(2022, 1, 19, aa); // 对象定义/对象实例化
//Date d1(2022, 1, 19, A(10)); // 对象定义/对象实例化
return 0;
}
运行结果对比:
解析:注释详细讲解了,分两种情况比较,使用初始列表和不使用初始化列表
四、成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<
A aa(1);
aa.Print();
}
输出1 随机值
explicit关键字构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还支持隐式类型转换。
// 隐式类型转换 - 相近类型 -- 表示意义相似的类型 double d = 1.1; int i = d; const int& i = d; //强制类型转换 - 无关类型 int* p = &i; int j = (int)p;
#includeusing 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; }
在语法上,代码中Date d1 = 2021等价于以下两句代码:
Date tmp(2021); //先构造 Date d1(tmp); //再拷贝构造
本来用2022构造一个临时对象Date(2022),再用这个对象拷贝构造d2,但是C++编译器在连续的一个过程中,多个构造会被优化,合二为一。 所以这里被优化为直接就是一个构造。
若是想禁止单参数构造函数的隐式转换,可以用关键字explicit来修饰构造函数。
explicit Date(int year = 0) //单个参数的构造函数
:_year(year)
{}
2.static成员
2.1概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的
成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
实现一个类,计算中程序中创建出了多少个类对象。
using namespace std;
class A
{
public:
A(int a = 0)
{
_a = a;
_scount++;
}
//静态成员函数,没有this指针
static int GetStaticCount()
{
return _scount;
}
private:
int _a;
static int _scount;//静态成员属于整个类
};
int A::_scount = 0;// 定义初始化
int main()
{
A a(1);
cout << a.GetStaticCount() << endl;// 对象访问
cout << A::GetStaticCount()<< endl;// 类域访问
return 0;
}
2.2特性
- 静态成员为所有类对象所共享,不属于某个具体的实例
- 静态成员变量必须在类外定义,定义时不添加static关键字
- 类静态成员即可用类名::静态成员或者对象.静态成员来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
【问题】
- 静态成员函数可以调用非静态成员函数吗?
不能,静态成员函数没有this指针,非静态成员函数需要传递this指针。
- 非静态成员函数可以调用类的静态成员函数吗?
可以。
3.C++11 的成员初始化新玩法C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变
量缺省值。
class B
{
public:
B(int b = 0)
:_b(b)
{}
private:
int _b;
};
// C++11 -- 打补丁
class A
{
public:
// 如果你再初始化列表阶段,没有对成员变量初始化,它就会使用缺省值初始化
A()
{}
private: // 要注意的是这里不是初始化,因为这里是声明,不能初始化
int _a1 = 0; // 这个里是给成员变量缺省值
B _bb1 = 10; // 隐式转换
B _bb2 = B(20);// 等价上条代码
int* p = (int*)malloc(4*10);
// vs2019支持
//int arr[10] = { 1, 2, 3, 4, 5 };
// 静态的不能这样给缺省值,必须在类外面全局位置定义初始化
// static int _sCount = 0;
};
int main()
{
A aa;
return 0;
}
友元
友元分为友元函数和友元类。友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元函数问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的
输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这
样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
ostream& operator<<(ostream& _cout)
{
_cout<
Date d(2017, 12, 24);
d<
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout<>(istream& _cin, Date& d)
{
_cin>>d._year;
_cin>>d._month;
_cin>>d._day;
return _cin;
}
int main()
{
Date d;
cin>>d;
cout<
友元函数说明:
1、友元函数可以访问类是私有和保护成员,但不是类的成员函数。
2、友元函数不能用const修饰。
3、友元函数可以在类定义的任何地方声明,不受访问限定符的限制。
4、一个函数可以是多个类的友元函数。
5、友元函数的调用与普通函数的调用原理相同。
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中非公有成员。
class A
{
// 在A类中声明B为友元类
friend class B;
public:
A(int a=0)
{
_a = a;
}
private:
int _a;
};
class B
{
public:
B(int b=0)
{
_b = b;
}
void SetAB(int a, int b)
{
_b = b;
_aa._a = a;//不在A类里加友元,无法访问A类对象的私有成员
}
private:
int _b;
A _aa;
};
我的成员函数可以访问友元类的私有成员。
友元类说明:
1、友元关系是单向的,不具有交换性。
比如上述A类和B类,在A类中声明B类为其友元类,那么可以在B类中直接访问A类的私有成员变量,但想在A类中访问B类中私有的成员变量则不行。
2、友元关系不能传递。
如果A是B的友元,B是C的友元,不能推出A是C的友元。
内部类
内部类概念
概念:如果一个类定义在另一个类的内部,则这个类被称为内部类。
注意:
1.此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类。
2、外部类对内部类没有任何优越的访问权限。
3、内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
内部类特性
1、内部类可以定义在外部类的public、private以及protected这三个区域中的任一区域。
2、内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3、外部类的大小与内部类的大小无关。
class A
{
private:
static int k;
int h;
public:
// 内部类
// 1、内部类B和在全局定义是基本一样的,只是他的受外部类A类域限制,定义在A的类域中
// 2、内部类B天生就是外部类A的友元,也就是B中可以访问A的私有,A不能访问B的私有
class B
{
// friend class A;
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
private:
int _b;
};
void f(B bb)
{
// A不是B的友元,不能访问B
bb._b;
}
};
int A::k = 1;
int main()
{
cout << sizeof(A) << endl;
A aa;
A::B bb;
return 0;
}
如果内部类与外部类有公有成员,可以选择放在外部类里,这样内部类和外部类都能访问。



