目录
const成员
将 const 修饰的 “ 成员函数 ” 称之为 const 成员函数 , const 修饰类成员函数,实际修饰该成员函数 隐含的 this 指针 ,表明在该成员函数中 不能对类的任何成员进行修改看看这段代码,我们如果去掉consth会有什么结果呢?
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << "Print()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
void Print() const
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void Test()
{
Date d1(2022, 1, 13);
d1.Print();
const Date d2(2022, 1, 13);
d2.Print();
}
不难发现对于d2来说,这就是一个权限放大的问题:
因为d2是const修饰的,而隐含的this指针的类型是Date* this,这就是之前所说的权限放大了。想要解决这个问题,我们就可以让this指针也带上const即可,这样权限平移就没有问题。这里const修饰的就是this指针。
取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需 要重载,比如想让别人获取到指定的内容!这个取地址操作符确实使用的频率很少,只需要大概知道怎么使用即可
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
看看结果:
再谈构造函数
构造函数体赋值
在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
初始化列表(重点)
class Time
{
public:
Time(int hour = 0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int day)
{}
private:
int _day;
Time _t;
};
int main()
{
Date d(1);
}
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();
}
看看结果,就会发现确实是按照变量声明的顺序进行初始化的。
_a2是最初声明的,而_a1是后声明的,还是个随机值,所以最终_a2就是随机值。
explicit关键字
class Date
{
public:
Date(int year)
:_year(year)
{}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022);
d1 = 2021;
return 0;
}
为什么这里的d1能直接赋值整形?
通过explicit来修饰之后,我们就没有办法整形进行赋值,因为不允许隐式转换。
static成员
概念声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 的 成员函数 ,称之为 静态成员函数 。 静态成员变量一定要在类外进行初始化有一个问题:实现一个类,计算程序中创建出了多少个类对象。我们可以通过static来修饰变量来实现
class A
{
public:
//构造
A()
{
++_count;
}
//拷贝构造
A(const A& a)
{
++_count;
}
~A()
{
--_count;
}
static int GetACount()
{
return _count;
}
private:
static int _count;
};
int A::_count = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
return 0;
}
通过结果不难了解到static的意义。
友元
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。友元分为: 友元函数 和 友元类
友元函数
在前面的日期类的实现的时候,我们尝试着去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的 输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作 数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成 全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
我们发现这和我们的常用调用不符合,所以这里就需要友元的知识点了
class Date
{
friend ostream& operator<<(ostream& _out, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year = 1, int month =1, int day =1)
: _year(year)
, _month(month)
, _day(day)
{}
// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
/*ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}*/
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& _cin, Date& d)
{
_cin >> d._year;
_cin >> d._month;
_cin >> d._day;
return _cin;
}
int main()
{
Date d1;
cin >> d1;
cout << d1;
return 0;
}
总结:
友元类
class Time
{
friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
这里就很能体现出了友元的弊端,就是封装性不能保证。
内部类
class A {
private:
static int k;
int h;
public:
class B // B天生就是A的友元
{
public:
void fo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
//这里B在A的类域,所以要加类域限定符
A::B b;
b.fo(A());
return 0;
}
匿名对象
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 aa2(10);
Solution().Sum_Solution(10);
return 0;
}
在我们只需要使用一次时,匿名结构体看起来会比较简洁。
拷贝对象时的一些编译器优化
class A {
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a(aa._a)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a = aa._a;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 传值返回
f2();
cout << endl;
// 隐式类型,连续构造+拷贝构造->优化为直接构造
f1(1);
// 一个表达式中,连续构造+拷贝构造->优化为一个构造
f1(A(2));
cout << endl;
// 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
A aa2 = f2();
cout << endl;
// 一个表达式中,连续拷贝构造+赋值重载->无法优化
aa1 = f2();
cout << endl;
return 0;
}
就拿其中一个进行解释:
看看结果就知道
我们发现最后它只调用了一次构造函数,但是对于连续拷贝构造加赋值重载就不能优化了,看最后一个的结果我们就知道编译器并没有进行优化。
再次理解类和对象
类是对某一类实体 ( 对象 ) 来进行描述的,描述该对象具有那 些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化 具体的对象 。就好像我们日常生活中的外卖系统一样,有多个对象,例如送外卖的人就是一个对象,客户也是一个对象,还有与客户对接的平台也是一个对象,这样简单的几个对象就组成了一个系统,最后再解释一下C++和C不同的一个重要的地方就是在于类和对象这样,C++是面向对象编程的,而C语言则是面向过程编程的。同时C++为了满足一些类和对象的使用,增加了一些新的语法,而这些语法大多都是因为类和对象而产生。这也说明了类和对象的重要性。