
🌈 say-fall:个人主页 🚀 专栏:《手把手教你学会C++》 | 《C语言从零开始到精通》 | 《数据结构与算法》 | 《小游戏与项目》 💪 格言:做好你自己,才能吸引更多人,与他们共赢,这才是最好的成长方式。
输入内容
之前我们在初始化成员变量时候都是在构造函数内部赋值初始化,而还有一种初始化方法是初始化列表:
类里面的成员变量int _hour;可以看作是成员变量的声明,而初始化列表就是成员变量的定义。
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};这里的 :_hour(hour)就是初始化列表,如果有多项成员变量的话,要用,隔开。所谓的中间就是在()和{}。
需要注意的是,有一些成员函数可以不在初始化列表里写(隐式);有一些成员函数是必须在初始化列表中初始化(显式):const修饰的变量、引用、没有默认构造的自定义类型
class Date
{
public:
Date(int& xx,int year, int month, int day)
//成员变量的定义(开空间)
//1. 初始化列表
:_year(year)
,_month(month)
,_day(day)
//必须初始化的
,_n(1)
,_ref(xx)
,_t(1)
//2. 函数体内赋值
{}
private:
//成员变量的声明
int _year;
int _month;
int _day;
//必须在初始化列表中初始化
const int _n;//const修饰变量
int& _ref;//引用
Time _t;//没有默认构造的自定义类型
};对于隐式和显式的初始化列表,程序执行时有如下逻辑:
1.初始化列表是成员变量【定义】的地方,一定会执行(哪怕自己没写,编译器也会生成空的初始化列表执行)
2.显式初始化:使用显式的初始化(就是写了初始化列表)
(形参缺省值的作用就是给显式的初始化使用的)
3.没有显式初始化:有缺省值:使用声明的地方的缺省值(声明处,而非形参)
没有缺省值:内置类型:不确定是否初始化,取决于编译器(随机值/野值)
自定义类型:调用自身的默认构造函数,没有默认构造的话直接编译报错
4.有三种成员【必须在初始化列表显式初始化】(缺一不可,写在函数体赋值编译报错):
const int _n;//const修饰的成员变量(只读,定义时必须赋值)
int& _ref;//引用成员变量(引用必须在定义时绑定对象,不可后期赋值)
Time _t;//无默认构造的自定义类型成员(编译器无法自动初始化)
5.初始化列表是按照【类内成员的声明顺序】初始化的,和初始化列表的书写顺序无关
#include<iostream>
using namespace std;
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
A(int a1)
:_a1(a1)
{}
//explicit A(int a1, int a2)
A(int a1, int a2)
:_a1(a1)
, _a2(a2)
{}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A& a)
:_b(a.Get())
{}
private:
int _b = 0;
};
int main()
{
A aa1 = 1;// 用1构造⼀个A的临时对象,再用这个临时对象拷⻉构造aa1(包含隐式类型转换)
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
aa1.Print();
const A& aa2 = 1;
// C++11之后才⽀持多参数转化
A aa3 = { 2,2 };
// aa3隐式类型转换为b对象
// 原理跟上⾯类似
B b = aa3;
const B& rb = aa3;
return 0;
}public、protected、private 访问限定符的限制。先来看一下静态成员变量:
class A
{
public:
//构造函数
A()
{
++_scount;
}
//拷贝构造
A(const A& t)
{
++_scount;
}
//析构函数
~A()
{
--_scount;
}
private:
// 类里面声明
static int _scount;
};
// 类外面初始化
int A::_scount = 0;下面看一下静态成员函数:
class A
{
public:
//构造函数
A()
{
++_scount;
}
//拷贝构造
A(const A& t)
{
++_scount;
}
//析构函数
~A()
{
--_scount;
}
//静态成员函数,没有this指针
static int GetACount()
{
return _scount;
}
private:
// 类里面声明
static int _scount;
};
// 类外面初始化
int A::_scount = 0;这里面的GetACount()就是静态成员函数
这里主要是掌握静态成员函数的两种用法:
A::GetACount()和a1.GetACount()(不推荐后一种)
还有一种是对象指针->GetACount(和a1.GetACount()一样不推荐)
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
A* p = &a1;
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
cout << p->GetACount() << endl;
//编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)
//cout << A::_scount << endl;
return 0;
}对比维度 | 静态成员函数(static 修饰) | 非静态成员函数(普通成员函数) |
|---|---|---|
所属对象 | 属于类本身,所有对象共享同一份函数拷贝 | 属于具体对象,每个对象都隐含绑定该函数的调用权限 |
this 指针 | 无 this 指针(不绑定任何对象) | 有隐含的 this 指针(指向调用该函数的对象) |
调用方式 | 1. 推荐:类名::函数名()(如 A::Print())2. 兼容:对象.函数名()(不推荐,语义混乱) | 必须:对象.函数名() 或 对象指针->函数名()(如 a.Print()/p->Print()) |
访问成员权限 | 只能访问类的静态成员(静态变量/静态函数),无法访问非静态成员(无this指针) | 可访问类的所有成员(静态+非静态),通过this指针访问非静态成员 |
生命周期 | 程序启动时创建,程序结束时销毁(和类的生命周期一致) | 随对象的创建/销毁而绑定/解绑(函数代码本身只有一份,调用时绑定this) |
内存存储位置 | 存储在全局/静态存储区(不和对象实例绑定) | 函数代码存储在代码段,调用时通过this指针关联对象 |
friend,并且把友元声明放到⼀个类的⾥⾯。关于友元类:
关于友元函数:
private/protected位置,那么A类就是B类的专属内部类,其他地⽅都⽤不了。class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main()
{
A aa1;
//A aa1();
// 不能这么定义对象,因为编译器⽆法识别下⾯是⼀个函数声明,还是对象定义
// 但是我们可以这么定义匿名对象,匿名对象的特点不⽤取名字,
// 但是他的⽣命周期只有这⼀⾏,我们可以看到下⼀⾏他就会⾃动调⽤析构函数
A();
A(1);
A aa2(2);
// 匿名对象在这样场景下就很好⽤,当然还有⼀些其他使⽤场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}下面提供一些代码用来观察编译器的优化:
class A
{
public:
A(int a = 0)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
void Print()
{
cout << "Print()->" << _a1 << endl;
}
A& operator++()
{
++_a1;
return *this;
}
private:
int _a1 = 1;
};
void f1(A aa)//传值传参,存在拷贝构造
{
}
A f2()
{
A aa(1);
//测试优化会不会有错误
++aa;
return aa;
}
int main()
{
//优化了,有类型转换:构造+拷贝构造->直接构造
A aa1 = 1;
const A& aa2 = 1;
cout << endl;
//没有优化:构造+拷贝构造
A aa3(1);
f1(aa3);
cout << endl;
//优化,匿名对象:构造+拷贝构造
f1(A(1));
cout << endl;
//优化,直接类型转换
f1(1);
cout << endl;
//优化, 不生成【拷贝的临时对象】,直接在返回值位置构造aa,省略1次拷贝构造
f2().Print();
cout << "************" << endl << endl;
//优化,不生成临时对象
A ret = f2();
ret.Print();
cout << "************" << endl << endl;
return 0;
}