🐇
🔥博客主页: 云曦
📋系列专栏:[C++]💨路漫漫其修远兮 吾将而求索 💛 感谢大家👍点赞 😋关注📝评论
文章目录
- 📔1、再谈构造函数
- 📰1.1、构造函数体赋值
- 📰1.2、初始化列表
- 📰1.3、explicit关键字
- 📔2、static成员
- 📰2.1、概念
- 📰2.2、特性
- 📔3、友元
- 📰3.1、友元函数
- 📰3.2、友元类
- 📔4、内部类
- 📔5、匿名对象
- 📔6、拷贝对象时编译器的一些优化
📔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 a)
:_a(a)
{}
private:
int _a;
};
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
,_ret(day)
,_i(10)
,aa(20)
{}
private:
int _year;
int _month;
int _day;
int& _ret; //引用 -> 语法规定:引用的对象必须初始化
const int _i; //const成员变量 -> 语法规定const的类型必须初始化
A aa;//没有默认构造 -> 成员变量是自定义类型时会去调用对应的默认构造,如果没有就会报错
};
(3)尽量使用初始化列表初始化,因为不管你用不用初始化列表,编译器都会走初始化列表。
class Time
{
public:
Time(int hour = 10)
:_hour(hour)
{
cout << "Time(int hour = 10)" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int year)
{}
private:
int _year;
Time time_t;
};
int main()
{
Date d1(2000);
return 0;
}
(4)成员变量在类中的次序就是初始化列表的次序,与初始化列表的次序无关。
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
: _day(day)
, _month(month)
,_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
初始化列表的执行次序
- 成员变量的次序与初始化列表次序不同会带来的问题
class stack
{
public:
stack(size_t n)
: _size(0)
, _capacity(n)
, _a((int*)malloc(sizeof(_a)* _capacity))
{
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
}
void push(int val)
{
_a[_size] = val;
_size++;
}
~stack()
{
free(_a);
_a = nullptr;
_size = 0;
_capacity = 0;
}
private:
int* _a;
size_t _size;
size_t _capacity;
};
int main()
{
stack st(10);
st.push(1);
st.push(2);
st.push(3);
//程序崩溃
//原因:malloc开空间时,计算所需的字节是用_capacity进行计算的
//但初始化列表的次序是跟着成员变量的次序走的,_capacity在_a的后面
//那么先走的就是对_a进行开空间,这时_capacity没有初始化,是随机值
//然后空间就有可能没有开对导致插入数据时程序崩溃
return 0;
}
- 所以大家在用初始化列表时,建议把初始化列表的次序和成员变量的次序写成一致,即实现了代码的可读性又避免了不必要的麻烦!
📰1.3、explicit关键字
- 构造函数不止具有构造和初始化对象的功能,对于单个参数或多个参数只有第一个参数没有缺省值其余均有缺省值的情况,还具有类型转换的作用。(简单来说就是单参数的构造函数支持隐式类型转换)
class Date
{
public:
Date(int year, int month = 1, int day = 1)
: _day(day)
, _month(month)
,_year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023);
//一个整型可以赋值给一个对象初始化
//都是源自于单参数的构造函数支持隐式类型转换
d1 = 2024;
return 0;
}
- explicit的作用就是不让他修饰的函数进行隐式类型转换
class Date
{
public:
explicit Date(int year, int month = 1, int day = 1)
: _day(day)
, _month(month)
, _year(year)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023);
d1 = 2024;
//编译报错: C2679: 二元“=”: 没有找到接受“int”类型的右操作数的运算符(或没有可接受的转换)
return 0;
}
📔2、static成员
📰2.1、概念
(1)用stack修饰的类成员称为类的静态成员。
(2)用stack修饰的成员变量称为静态成员变量。
(3)用stack修饰的成员函数称为静态成员函数。
注意:定义静态成员变量时,必须在类外进行初始化。
class A
{
public:
A(int a = 0)
:_a(a)
{}
int Get()
{
return _a;
}
private:
int _a;
};
class B
{
public:
static void Print() //静态成员函数
{
cout << _aa.Get() << endl;
cout << _b << endl;
}
private:
static A _aa;//类的静态成员
static int _b;//静态成员变量
};
//静态成员变量需要在类外初始化
int B::_b = 10;
A B::_aa = 20;
int main()
{
A aa1;
B::Print();//静态成员函数的调用
return 0;
}
- 那么static修饰的对象究竟有什么用了?
- 这里有一个问题可以很好的讲解static的作用;
- 实现一个类,计算出程序里创建出了多少个类对象
class A
{
public:
A()
{
A::_count++;
}
A(const A& a)
{
A::_count++;
}
~A()
{
A::_count--;
}
static int GetCount()
{
return _count;
}
private:
static int _count;
};
这里分两种情况:
(1) 如果GetCount函数不是静态成员函数
int main()
{
//3个类对象
A aa1;
A aa2(aa1);
A aa3;
//如果GetCount函数不是静态成员函数
//编译报错:error C2352:
// “A::GetCount”: 调用非静态成员函数需要一个对象
cout << A::GetCount() << endl;
//GetCount函数不是静态成员函数
//多定义一个对象用这个对象.调用
//打印时要-1因为这个a这个对象
//是我们实例化出来调用GetCount静态成员函数的
A a;
cout << a.GetCount() - 1 << endl;
return 0;
}
(2) 如果GetCount函数是静态成员函数
int main()
{
//3个类对象
A aa1;
A aa2(aa1);
A aa3;
//如果GetCount函数是静态成员函数
//即可以通过一个对象调用
A a;
cout << a.GetCount() - 1 << endl;
//也可以用类名指定调用
cout << A::GetCount() << endl;
return 0;
}
- 具体为何GetCount不是静态成员函数就不能用类名指定调用,且看static的特性。
📰2.2、特性
- 静态成员被所有类对象共享,不属于任何一个对象,存在静态区
- 静态成员变量必须在类外定义,定义时不加static关键字,类内只是声明
- 类的静态成员可以用 类名::静态成员 或者 对象.静态成员 进行访问
- 静态成员函数没有this指针,不能访问非静态成员
- 静态成员本质也是类的成员,受public、protected、private访问限定符限制
注意:
(1) 静态成员函数不可以调用非静态成员函数。
(2) 非静态成员函数可以调用静态成员函数
class A
{
public:
A(int a = 0)
:_a(a)
{}
static void func1()
{
func2();//编译报错:error C2352:
//“A::func2”: 调用非静态成员函数需要一个对象
}
void func2()
{
func1();//程序正常运行
}
private:
int _a;
};
int main()
{
A aa(10);
return 0;
}
📔3、友元
- 友元提供了一种突破封装的方式,有时提供了便利,但友元会增加耦合度,破坏了封装,所以友元不宜多用。
- 耦合度讲解:
- 友元分为友元函数和友元类
📰3.1、友元函数
- 在类和对象中篇,讲解operator<<和operator>>重载时,因为实现成成员函数时,this指针占用了第一个参数导致cout无法成为第一个参数,所以只能把operator<<和operator>>重载实现在类外,但类外无法访问到类内的成员变量,这时用友元解决了这里的问题。
class Date
{
friend ostream& operator<<(ostream& out, Date& d);
friend istream& operator>>(istream& in, 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& out, 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(2024,6,2);
cout << d1 << endl;
return 0;
}
- 友元函数可以直接访问类内的私有成员,定义在类外的函数需要在类内声明且声明时需要加上friend关键字。
- 注意:
(1) 友元函数可以访问类内的私有和保护成员,但不能访问类内的成员函数
(2) 友元函数不能用const修饰
(3) 友元函数可以定义在类定义的任何地方,不受访问限定符限制。
(4) 一个函数可以是多个类的友元函数
(5) 友元函数的调用与普通函数调用是一样的
📰3.2、友元类
友元类的所有成员函数都可以访问另一个类中的非公有成员(公有成员类内类外都可以访问)。
- 友元类的关系是单相的,不具有交换性
class A
{
//声明B类是A类的友元,B类可以访问A类的私有成员变量,
//但A类无法访问B类的私有成员
friend class B;
public:
A(int a = 100)
:_a(a)
{}
void Print()
{
cout << "_a:" << _a << endl;
cout << "_b:" << bb._b << endl;
//编译报错:
//error C2079: “A::bb”使用未定义的 class“B”
}
private:
int _a;
B bb;
};
class B
{
public:
B(int b = 100)
:_b(b)
{}
void Print()
{
cout << "_a:" << aa._a << endl;
cout << "_b:" << _b << endl;
}
private:
int _b;
A aa;
};
- 友元关系不能传递
举例:
- 有A、B、C三个类,B类是A类的友元,C类是B类的友元,在A类里没有C类友元声明的情况下,C类不会是A类的友元。
class A
{
friend class B;
public:
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
class B
{
friend class C;
public:
B(int b = 0)
:_b(b)
{}
void funB()
{
_aa._a;//B是A的友元,可以访问A类的私有成员变量
}
private:
int _b;
A _aa;
};
class C
{
public:
C(int c = 0)
:_c(c)
{}
void funC()
{
_bbb._b;//C是B的友元,可以访问B类的私有成员变量
_aaa._a;//C不是A的友元,不可以访问A类的私有成员变量
//编译报错:
//error C2248: “A::_a”:
//无法访问 private 成员(在“A”类中声明)
}
private:
int _c;
A _aaa;
B _bbb;
};
- 友元关系不能继承
这里关联到了继承,等到继承篇再和大家讲解。
📔4、内部类
- 概念:在一个类内定义的类称为内部类,内部类就是一个独立的类,它不属于外部类,外部类对内部类没有任何优越的访问权限。
- 注意:内部类天生就是外部类的友元,内部类可以访问外部类的私有成员变量,但外部类无法访问内部类的私有成员变量。
class A
{
public:
class B
{
public:
B(int b = 0)
:_b(b)
{}
void funB(const A& aa)//B天生是A的友元
{
aa._a;
}
private:
int _b;
};
A(int a = 0)
:_a(a)
{}
private:
int _a;
};
- 特性:
- 内部类可以定义在外部类的public、protected、private都是可以的。
class A
{
public:
class B
{
public:
void funa(const A& aa)
{
aa._a;
}
protected:
void funb(const A& aa)
{
aa._a;
}
private:
void func(const A& aa)
{
aa._a;
}
private:
int _b;
};
private:
int _a;
};
- 内部类可以访问外部类的static修饰的成员,不用指定类名或外部类的对象。
class A
{
public:
class B
{
public:
void func(const A& aa)
{
cout << aa._a << endl;
cout << _n << endl;
}
private:
int _b;
};
private:
int _a;
static int _n;
};
int A::_n = 0;
- sizeof(外部类)不会计算内部类,和内部类没有任何关系。
class A
{
public:
class B
{
public:
void func(const A& aa)
{
cout << aa._a << endl;
}
private:
int _b;
};
private:
int _a;//4字节
};
int main()
{
cout << sizeof(A) << endl;//编译通过,打印4
return 0;
}
📔5、匿名对象
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a = 0)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A aa1(10);//实例化对象
//匿名对象
//匿名对象的特点就是不用给对象起名字
//且匿名对象的生命周期只在这一行
//过了这一行就会调用析构函数
A(20);
A aa2(1);
return 0;
}
- 匿名对象的使用场景
class A
{
public:
int A_func(int n)
{
//进行累加之类的操作...
return n;
}
};
int main()
{
A().A_func(10);
return 0;
}
STL篇章还会有其他的使用场景。
📔6、拷贝对象时编译器的一些优化
- 在传参和传返回值时,现在的编译器一般都会做的一些优化,这些优化在一些场景下还很有用。
- **注意:**我这里用的是vs2022来演示的,不同的编译器不同,可能会优化也可能不优化。
- 这个优化就是:在同一个表达式内:
- 构造+构造 -> 构造(两次构造会优化成一次构造)
- 构造+拷贝构造->构造(一次构造和一次拷贝构造会优化成一次构造)
- 拷贝构造+拷贝构造->拷贝构造(两次拷贝构造会优化成一次拷贝构造)
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
_a = a._a;
}
A& operator=(const A& a)
{
cout << "A& operator=(const A& a)" << endl;
if (this != &a)
{
_a = a._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
- 场景1
void func1(A aa)
{}
void scene1()
{
//单参数的构造函数支持隐式类型转换
//隐式类型转换,构造一个临时对象
//用这个临时对象再去拷贝构造给aa
//构造+拷贝构造->优化成一个构造
func1(10);
}
- 场景3
A func2()
{
A aa(10);
return aa;
}
void scene2()
{
//func2函数的aa对象是一个局部对象
//传值返回这个对象会拷贝构造出一个临时对象返回
//返回的临时对象再去拷贝构造a2
//连续的拷贝构造+拷贝构造->被优化成一次拷贝构造
A a2 = func2();
}
- 场景3
A func2()
{
A aa(10);
return aa;
}
void scene3()
{
//传值返回,拷贝构造一个临时对象
//再用这个临时对象去赋值拷贝给a3
//拷贝构造 + 赋值拷贝 -> 无法优化
A a3;
a3 = func2();
}