const修饰对象const修饰成员函数我们知道在成员函数中如何保护参数的值不被修改,只需要在**参数的前面写上const**修饰即可。
但是,如何保护this的成员变量不被修改呢,可能有同学说我在this指针前面加一个const不就可以了吗,可是this指针是一个隐藏的指针,你没法办像之前那样加const,c++规定了一个语法规则,这样加const即可保护this里面的成员变量不被修改
代码演示:
#include
using namespace std;
class Date
{
public:
Date(int year = 2003, int month = 6, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
printf("%d-%d-%d\n", _year, _month, _day);
}
void Print() const
{
printf("%d-%d-%d\n", _year, _month, _day);
}
void f() const
{
//_year = 10;//error--const修饰成员函数无法改动成员变量
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
const Date d2(2022, 9, 5);
//这个就是说d2这个对象里面的成员变量是不允许通过成员函数被修改的
d2.Print();
return 0;
}
const Date d2(2022, 9, 5);->这个就是说d2这个对象里面的成员变量是不允许通过成员函数被修改的
语法规则:在成员函数后面加上const即可,上面两个Print成员函数也属于函数重载,d1对象调用的是非const的print成员函数,d2对象调用的是const的成员函数。
const修饰成员函数无法改动成员变量
图解演示:
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IfXsmtuI-1662541834340)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220907104505001.png)]](https://1000bd.com/contentImg/2023/11/04/100440022.png)
const与非const对象与成员函数之间的相互调用关系对象与函数之间
const对象可以调用非const成员函数吗? F–权限放大
非const对象可以调用const成员函数吗? T–权限缩小
const成员函数内可以调用其它的非const成员函数吗?
F–权限缩小
非const成员函数内可以调用其它的const成员函数吗?
T–权限放大
大家可以自己用程序去测试上面的关系!
非const&运算符重载,const运算符重载
代码演示:
#include
using namespace std;
Date* operator&()
{
return this;
}
const Date* operator&() const
{
return this;
}
编译器生成的是完全够用的
#include
using namespace std;
Date* operator&()
{
return nullptr;
}
const Date* operator&() const
{
return nullptr;
}
由于类的封装性(访问限定符),类外部的函数是无法访问类的私有数据成员,但是有些时候我们又不得不让某个外部函数可以访问类的私有成员,友元函数就来了。就像是你配了一把你家钥匙给外面的人。
所以建议不到万不得已是不要用友元的。
友元函数可以直接访问类的私有成员
它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加friend关键字。
#include
using namespace std;
class Date
{
friend void F(Date d);
public:
Date(int year = 2003, int month = 6, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void F(Date d)
{
d._year = 0;
d._month = 0;
d._day = 0;
}
当然了,上面的代码没有什么实际意义,只是为了大家可以理解以下友元函数的语法规则。
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。 比如下述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
代码演示:
#include
using namespace std;
class Time
{
friend class Date; // 声明日期类为时间类的友元类,那么日期类的成员函数全是时间类的友元函数,则在日期类中就直接访问Time类中的私有成员变量
public:
Time(int hour = 0, int minute = 0, int second = 0)
{
_hour = hour;
_minute = minute;
_second = second;
}
void f(Date& d)
{
//d._year = 1;//error,Date类是Time类的友元类,而Time类不是Date类的友元类,无法访问其私有成员
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 0, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
_t._hour = 1;//在Date类中访问Time类的私有成员
}
void SetTimeOfDate(int hour, int minute, int second)//在Date类中访问Time类的私有成员
{
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
return 0;
}
cout<<,cin>>那么首先我们得理解内置类型的cout<<,cin>>的实现原理是什么?
####内置类型的cout<<,cin>>的实现原理
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iHfnHWJ3-1662541834341)(D:\gitee仓库\博客要用的截图\屏幕截图 2022-09-07 093110.png)]](https://1000bd.com/contentImg/2023/11/04/100440065.png)
1.首先**istream,与ostream是两个类**,而**cout与cin是两个类定义出来的对象**(当然了,这些对象在using namespace std里面就已经被定义出来了),这就是为什么你不写using namespace std他不让你使用cout的原因。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Ss74axO-1662541834342)(C:\Users\Cherish\AppData\Roaming\Typora\typora-user-images\image-20220907111457411.png)]](https://1000bd.com/contentImg/2023/11/04/100440062.png)
2.其次呢,#include,是包含了std里面的一些运算符重载,其中就包括了,>> <<,当然了里面的实现就比较复杂,咱们不做深的讨论。
代码实现:
#include
using namespace std;
class Date
{
public:
Date(int year = 2003, int month = 6, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
void operator<<(ostream& out)//void operator<< (Date* this,ostream& out)
{
out << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2022,9,5);
d1 << cout;
return 0;
}
可是这跟我们的使用习惯不一样,我们的习惯是将cout放在前面,d1放在后面
运算符重载,操作数的位置是这样规定的:根据实参的位置而定,例如:假如有两个操作数,第一个操作数是第一个形参,第二个操作数是第二个实参。
可是如果我们将<<运算符重载写成成员函数的话,第一个参数就会一直被this指针所占居,无法满足我们的需求。
所以我们要将 <<运算符重载写成全局的,这样就可以让cout去占到第一个操作数的位置了。
但是写成全局的是无法访问Date类里面的成员变量的,所以咱们要将这个函数声明成日期类的友元函数,这样才可以。
代码实现:
#include
using namespace std;
class Date
{
friend ostream& Date::operator<<(ostream& out, const Date& d);
public:
Date(int year = 2003, int month = 6, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
void Print() const
{
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out,const Date& d)//为了访问Date,必须还要将其声明成是Date的友元函数
{
out <<d. _year << "-" << d._month << "-" << d._day << endl;
return out;
}
int main()
{
Date d1;
Date d2(2022,9,5);
cout << d1 << d2 << endl;
return 0;
}
其中out是cout的别名,只是为了在函数中进行区分,写成cout也是可以的
代码演示:
#include
using namespace std;
class Date
{
friend ostream& Date::operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
//友元并不是说,operator是Date类里面的,可以理解成好朋友吧,然后可以访问朋友的成员变量
public:
Date(int year = 2003, int month = 6, int day = 10)
{
_year = year;
_month = month;
_day = day;
}
void Print() const
{
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
};
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
ostream& operator<<(ostream& out,const Date& d)
{
out <<d. _year << "-" << d._month << "-" << d._day << endl;
return out;
}
int main()
{
Date d1;
Date d2(2022,9,5);
cout << "输入年份d1,d2的值" << endl;
cin >> d1 >> d2;
cout << d1 << d2 << endl;
return 0;
}
其中in是 cin 的别名,只是为了在函数中进行区分,写成cin也是可以的
咱们先看看两种初始化的语法规则:
#include
using namespace std;
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;
};
#include
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year = 2022, int month = 9, int day = 6)
//初始化列表为成员变量定义的地方,创建的地方
:_year(year)
, _month(month)
, _day(day)
, _ref(_year)
, _aa(10)//引用必须在定义的时候就初始化,所以只能在初始化列表定义
, _n(10)
{
A aaa(20);
_aa = aaa;
_year = 2;
_month = 1;
_day = 1;
}
private:
int _year;
int _month;
int _day;
//那些必须在定义的时候就初始化的成员变量,必须在在初始化列表中,初始化
int& _ref;
A _aa;
const int _n;
};
int main()
{
Date d1(2003, 6, 10);
return 0;
}
引用成员变量
const成员变量
自定义类型成员(该类没有默认构造函数)–
有默认构造函数的时候,自定义类型是可以在函数体内初始化的,但是这样就麻烦了,不如直接在初始化列表初始化好。
初始化列表为成员变量定义的地方,所以严格点来讲,函数体内初始化并不是初始化,而是对成员变量的再处理。
所以就会有引用成员变量,const成员变量只能在初始化列表初始化,因为他们的语法规定是:在定义的同时就必须完成初始化,后面是无法再对他们的值进行处理的。
而_year _month _day他们在初始化列表初始化完成之后,还可以在函数体内进行在处理,所以他们可以不在初始化列表初始化。
总结:建议都是用初始化列表初始化
初始化列表初始化成员变量的顺序:
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关
下面有一个题,大家可以做一做:
代码演示:
#include
using namespace std;
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();
}
A.输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值
我们先来看看内置类型的隐式类型转换
#include
using namespace std;
int main()
{
int i = 4;
double j = 4.4;
j = i;
return 0;
}
自定义类型也有隐式类型转换:
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
private:
int _a;
};
class Date
{
public:
Date(int year = 2022, int month = 9, int day = 6)
:_year(year)
, _month(month)
, _day(day)
, _aa(10)
{
A aaa(20);
_aa = aaa;//这个运用赋值运算符重载对_aa进行再处理
_aa = 30;//这个语法c++是支持的,这个是隐式类型转换,原理是 A tmp(30) + _aa = tmp
//构造+赋值重载
//编译器进行优化后把这句代码优化成了直接调用构造函数了
}
private:
int _year;
int _month;
int _day;
A _aa;
};
int main()
{
Date d1(2003, 6, 10);
return 0;
}
隐式类型转换的实质是 A tmp(30) + _aa = tmp,先构造,再赋值
但是编译器优化之后,将隐式类型转换优化成了直接调用构造函数了,这个是可以用程序去验证的。
当我们不想支持这种隐式类型转换的时候,可以在函数的前面加上explicit这个关键字
explicit A(int a = 0)
:_a(a)
{
cout << "A()" << endl;
}
我们来回顾以下,我们平时是如何来调用成员函数的?
是创建一个对象,让对象取调用成员函数,而且不同的对象调用的是同一个函数。
但是有时候的需求是:是需要调用一下函数,不需要使用对象,这个时候匿名对象就起作用了。
class Date
{
public:
Date(int year = 2022, int month = 9, int day = 6)
:_year(year)
, _month(month)
, _day(day)
{}
void Print()const
{
printf("%d-%d-%d\n", _year, _month, _day);
}
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
//匿名对象的生命周期在这一行
int main()
{
Date d1;
d1.Print();
Date d2(2003, 6, 10);
d2.Print();
Date().Print();//这里创建的是一个匿名变量,可以写一个析构函数看一下其生命周期
return 0;
}
static成员static成员变量声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;
静态成员变量是在类外面进行初始化的
大家可以猜想以下这个A类的大小:
#include
using namespace std;
class A
{
public:
void func()
{
//…………
}
private:
static int _n;
};
int _n = 0;
int main()
{
cout << sizeof(A) << endl;
return 0;
}
屏幕输出了一个1,1是用来占位的,说明静态成员变量不是定义在对象中的,而是定义在静态区的。
所以构造函数中的初始化列表并没有定义静态成员变量。
并且这个静态成员变量是所有权是类的所有成员,所有对象都可以访问,所有对象访问的都是一致的。
用static修饰的成员函数,称之为静态成员函数。
public、protected、private3种访问级别,也可以具有返回值【问题】
面试题:实现一个类,计算中程序中创建出了多少个类对象。
需要一个计数器,为了保证这个计数器是类专有的,我们定义成静态成员变量
class Date
{
public:
Date(int year = 2022, int month = 9, int day = 6)
:_year(year)
, _month(month)
, _day(day)//创建对象必然会调用构造函数,所以在构造函数中,对静态成员进行处理
{
++_count;
}
void Print()const
{
printf("%d-%d-%d\n", _year, _month, _day);
}
private:
int _year;
int _month;
int _day;
static int _count;//静态成员变量
};
//int _count = 0;//error
//注意,这样写,这个_count就毫无意义了,由于访问限定符的存在,只可以是Date类访问
int Date::_count = 0;//初始化_count只有这一个后路
int main()
{
Date d1;
Date d2;
Date d3;
Date d4;
Date d5;
Date().Print();
cout << d5.Get_Count() << endl;
cout << d1.Get_Count() << endl;
cout << d2.Get_Count() << endl;
Date();
cout << Date::Get_Count() << endl;//静态成员函数特有的调用方式,直接用类名访问
cout << Date().Get_Count() << endl;//这里由于创建了一个匿名变量_count的值又多了一次
return 0;
}
#include
using namespace std;
class B
{
public:
B(int x = 0)
:_x(x)
{
}
private:
int _x;
};
class A
{
public:
A(int a = 10, double b = 3.14)
, _p(nullptr)
, __b(3)
{
_a = 100;
}
private://这里在生命成员变量的时候给了缺省值,当初始化列表里面没有初始化处理时,就会使用缺省值
//顺序:先走初始化列表(当初始化列表没有对应的初始化,就会去走缺省值),再走函数体内
int _a = 9;
double _b = 5.5;
int* _p = nullptr;
int* a = (int*)malloc(sizeof(int)* 4);
B __b = 5;//隐式类型转换,编译器优化后是直接调用构造函数
static int n ;//但是静态成员变量不可以这样用,因为静态成员不在初始化列表定义,而是在全局区
};
int A::n = 1;
int main()
{
A aa;
return 0;
}
上面的写法与之前不同的地方是:在成员变量声明时给了缺省值,这个缺省值会在初始化列表中发挥作用。上述例子,初始化列表没有对成员a,成员b进行初始化处理,那么就会按照声明处给的缺省值,对成员a,b进行处理,并且指针类型,动态内存,自定义类型都可以这样用。
大家可以对上述程序进行调式,了解其用法。
理解这句话即可–内部类天生外部类的友元类,而外部类不是内部类的友元类
#include
using namespace std;
class Date
{
public:
Date(int year = 2022 ,int month = 9 ,int day = 8)
:_year(year)
, _month(month)
, _day(day)
{}
class Time
{
public:
void f(const Date& d)
{
_hour = 10;
_minute = 10;
_second = 10;
cout << d._year <<"-"<< d._month<< "-" << d._day << endl;
cout << _count << endl;//同样也可以访问静态的成员变量
}
private:
int _hour;
int _minute;
int _second;
};
void f2(Time t)
{
//cout << t._hour;//外部类不是内部类的友元
}
private:
int _year;
int _month;
int _day;
static int _count ;
};
int Date::_count = 100;
int main()
{
Date d;//用Date创建一个对象
Date::Time t;//用Time创建一个对象,Time这个类是Date的内部类,访问方式是::
t.f(d);
return 0;
}
截至到此,咱们的类与对象就彻底地结束了,博主一共总结了
类与对象(上)–介绍类,并且怎样写类
类与对象(中)–六大默认成员函数,以及运算符重载 重点
类与对象(下)–类的一些其他语法
下面给出几道对应的OJ题目,大家可以用来巩固知识点,如果你实现了日期类的各功能实现,那么这几个题目其实还好,没写的可以参考我的gitee:2.3日期类各功能实现/2.3日期类各功能实现 · small_sheep/cplusplus0study - 码云 - 开源中国 (gitee.com)
但是不要陷入到日期类里面去了,不要做每个题都把自己的日期类粘贴过来,想想其他方法。
1.求1+2+3+…+n_牛客题霸_牛客网 (nowcoder.com)
2.计算日期到天数转换_牛客题霸_牛客网 (nowcoder.com)
3.日期差值_牛客题霸_牛客网 (nowcoder.com)
4.打印日期_牛客题霸_牛客网 (nowcoder.com)
5.日期累加_牛客题霸_牛客网 (nowcoder.com)
OJ题目代码也已经放入gitee仓库中:
类与对象OJ题源码.png · small_sheep/cplusplus0study - 码云 - 开源中国 (gitee.com)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pXGDqNrQ-1662541834343)(D:\gitee仓库\博客使用的表情包\给点赞吧.jpg)]](https://1000bd.com/contentImg/2023/11/04/100439996.jpeg)
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sz8hHCbd-1662541834344)(D:\gitee仓库\博客使用的表情包\要赞.jpg)]](https://1000bd.com/contentImg/2023/11/04/100440065.jpeg)