Th3.3:类的相关基础知识点详述-3th(重要)

 本博客将记录:的相关知识点的第3节的笔记!

(这个在学习C++基础课程时已经学习过一次了,这里再次简单地回顾一下而已)

今天总结的知识分为以下5个点:

  一、在类定义中实现成员函数inline
  二、成员函数末尾的const
  三、mutable关键字
  四、返回对象自身的引用 
以及 this指针
  五、static成员

  一、在类定义中实现成员函数inline:

        关于inline内联函数关键字的基础详解,我在这篇博客中已经总结到位,如果不熟悉的小伙伴不妨来看看我这篇博客:

C++新经典课程学习笔记之第二章-2.6小节之 二、内联inline函数

        即:编译器会将内联函数被调用的语句直接替换为该函数的函数体内的all语句

#include<iostream>
using namespace std;
//inline内联函数
inline void func(int a) { cout << a << endl; }
int main(void) {
	func(5);//直接用函数体内的语句 cout << a << endl; 替换 func(5);这条语句!
	return 0;
}

        inline关键字的目的其实就是为了去提高你程序运行的效率。因为inline了的函数被调用的语句会在编译阶段就给替换为对应的函数体内的all语句,这样后序就不涉及什么函数传参和计算等问题了。

        现在,我总结的是,在类的定义中如何去实现inline内联函数:

        直接在类.h头文件中将函数具体的实现写了的函数,即为类中的inline内联函数(或说相当于该函数可能被编译器当作是inline函数来处理。因为,一个函数能否真正地成为内联函数关键在于编译器自身的决定,这我们控制不了。我们开发者给函数加inline只是对于编译器的一种建议而已,至于编译器采纳与否关键还是看其自身

比如:

#ifndef __TIME_H__
#define __TIME_H__
#include<iostream>
using namespace std;
class Time {
public:
	int m_Hour;//时
	int m_Minute;//分
	int m_Second;//秒
public:
	Time(int h,int m,int s) :m_Hour(h), m_Minute(m), m_Second(s){}
    //==> inline Time(int h,int m,int s) :m_Hour(h), m_Minute(m), m_Second(s){}
public:
    //这就是类中的内联函数的写法
    void func(int h) { this->m_Hour = h; }//==>inline void func(int h) {this->m_Hour = h;}
};
#endif

  二、成员函数末尾的const(即为:常量(常/const)成员函数):

        关于这个部分的内容,我其实在之前的一篇文章中已经总结得很详细了,这里就不多赘述了。如果忘记了的小伙伴可以看我下面这篇博客:

简单总结 常(常量/const)成员函数 and 常对象的使用

        注意一下const加在函数末尾的使用场景:只有成员函数的末尾可以加const,并使之成为常量成员函数。不能加在一个普通函数的末尾。对于一个普通函数的末尾加const是错误的!编译不通过!

void normal_Func()const{}//错!

 

  三、mutable(翻译:易变的、可变的)关键字(与const常量关键字是反义词的关系)

        mutable关键字的引入其实本质上就是为了突破之前const关键字的限制的。

正经地说:mutable可以突破 const常成员函数 与 常对象 都不能修改类中all的非static类型的成员变量这样一条限制。

不正经地说:mutable修饰的成员变量,永远处于可被修改的状态!即便是在const常量成员函数中,这个mutable修饰的成员变量也可以被修改!

(因为mutable,英文翻译就告诉我们这个是容易改变的意思哈哈哈)

person_name.h

#ifndef __PERSON_NAME_H__
#define __PERSON_NAME_H__
#include<iostream>
#include<string>
using namespace std;
class person_Name {
private:
	string m_Name;
public:
	person_Name() :m_Name("") {}
	person_Name(string name):m_Name(name){}
	string getName() {
		return this->m_Name;
	}
};
#endif

Person.h

#ifndef __PERSON_H__
#define __PERSON_H__
#include<iostream>
#include"person_name.h"
using namespace std;
class Person {
public:
	mutable int m_Age;
	mutable person_Name m_Name;
public:
	static int m_girlFriendNums;
	//当然,static类型的成员变量只可以在类内声明,类外初始化
public:
	Person(int age,const person_Name& name):m_Age(age), m_Name(name){}
	void showInfo() const;
	void showInfo();
};
#endif

Person.cpp

#include"Person.h"
int Person::m_girlFriendNums = 0;
void Person::showInfo() const {
	cout<< (m_Age = 1998) <<endl;//在常成员函数中修改mutable关键字修饰的成员变量
	cout << m_girlFriendNums << endl;
	cout << this->m_Name.getName() << endl;
}
void Person::showInfo(){
	cout<< (m_Age = 1999) <<endl;//在非常成员函数中修改mutable关键字修饰的成员变量
	cout << m_girlFriendNums << endl;
	cout << this->m_Name.getName() << endl;
}

main.cpp

#include<iostream>
#include"Person.h"
using namespace std;
int main(void) {
	Person const p1(1, person_Name("lyf"));
	p1.showInfo();
    //1998 0 lyf
	Person p2(2,person_Name("lzf"));
	p2.showInfo();
    //1999 0 lzf
	return 0;
}

 

四、返回对象自身的引用 以及 this指针:

        要理解对象自身的引用,首先你得知道什么是this指针?

满足以下2个条件就是所谓的this指针

①this指针是在c++的类中隐藏起来的一种指针常量

(顾名思义,指针是个常量,也即this指针所指向的对象的地址是不可变的!一定是指向 调用类中某成员函数or某成员变量的对象的,而不能乱指向别的对象。因此,每个对象之间的每一份数据都是相互独立的,互不影响的了。)

//类似下面这种int型的指针常量:
int a = 1,b = 2;
int* const this_s= &a;
this_s = &b;//错×!因为指针常量是指针(指向的对象的地址)就是个常量的意思,也即不能修改的意思
//你这里修改了那肯定是会报错的!

②this指针本身就指向调用该类的某成员函数or某成员变量的对象。

wjw老师说过的一句话我认为特别妙:

        在编译器的角度看,任何对于类的成员(成员变量和成员函数)的访问 都被看作是 对于this指针的隐式调用!

那么,什么是隐式调用呢?请看以下代码:

//仍然拿上述的Person类做例子
//类的内部
Person& add_Age(int addage){
	m_Age += addage;//==> this->m_Age += addage; 
	return *this;
}
//==>就相当于
Person& add_Age(Person* const this,int addage){
//当然这只是一段伪代码,this是关键字,你肯定不能这样写哈哈哈
	this->m_Age += addage;//==> m_Age += addage; 
	return *this;
}
//这就是编译器帮我们隐藏起来的关于this指针的代码
//外界(类的外部)
Person p(20,person_Name("tjr"));
p.add_Age(3);//==>p.add_Age(&p,3);
//output: 23 0 lzf

对象自身的引用其实就是给对象本身起一个别名 or 说是给*this起一个别名(本质都一样!)

对于刚才举的例子里的代码:

//调用该函数的对象的地址 == this指针指向的空间 == 返回的对象自身的引用的地址
&p == this == &( p.add_Age(3) )

相信通过以上的代码讲解,你理解了啥是返回对象本身的引用以及this指针了。

还有一个重要问题是:为啥我们要返回对象本身的引用呢?

因为返回对象本身的引用可以使得我们可以do类似于 a+b+c+d... 这样的链式运算,也即:

返回对象本身的引用,符合链式编程的思想。

比如请看以下代码:

//每一个p2.add_Age(3) == 一个p2对象
//只不过这是age加了3之后的p2对象而已
p2.add_Age(3).add_Age(3).add_Age(3).showInfo();
//==>
p2.add_Age(3);
p2.add_Age(3);
p2.add_Age(3);
p2.showInfo();
//==>
p2.add_Age(3*3).showInfo();
//==>
p2.add_Age(3*3);
p2.showInfo();

如果说你只是返回Person对象本身,而没有返回其引用

Person Person::add_Age(int addage) {
	m_Age += addage;
	return *this;
}

则编译器会将*this这个Person对象的值赋值给一个匿名的Person对象(先在栈区开辟空间存放该匿名对象,再do赋值),然后再将该匿名的Person对象return 回去。那么此时this指向的对象的地址和该return回来的对象的地址就是2码事了!

也即:

//调用该函数的对象的地址 == this指针指向的空间 != 返回的对象自身的地址
&p == this != &( p.add_Age(3) )

此时,你对这个新地址存放的匿名对象就不可以do链式编程的工作了,也即不能像

a+b+c+d..这样do事情了。比如:

p2.add_Age(3).add_Age(3).add_Age(3).showInfo();//×!
//错误!返回的p2.add_Age(3)根本就不是原来的p2了,因为return的匿名对象和p2这个原对象的地址不同了
//so 不可以do 连续的链式的编程工作了!

补充知识:对于this指针的使用有3条注意事项

①this指针只能在成员函数中使用,全局函数静态static函数都用不了!

②在const成员函数中,this指针是一个 常量 指针常量。

        在本篇博客的例子中,也即是(const Person* const this;)因为本来this指针是一个指针常量,你只是不可以修改其指向的对象而已,但是你可以修改其指向对象的内容。当在一个const常成员函数中使用this时,由于常成员函数本来就规定了在其函数体内一定不可修改类的成员(成员函数or成员变量)。因此,此时在该函数内的this指针就顺利成章地成为一个常量指针了(因为此时就连this指向的对象的内容也不可以修改了),综合起来this指针就是一个常量指针常量了。

③平时在成员函数中使用成员变量时,最好还是加一个this指针(当然你也可以通过规范你编写的成员变量名规避这样的问题),否则有可能会出现传入的参数与成员变量同名的case。比如:

Person& Person::add_Age(int m_Age) {
	m_Age += m_Age;
    //如果传入的参数与我类中的成员变量重名时
    //编译器会认为这个m_Age就是你传入的参数,也即左边和右边都认为的传入的参数,而不是成员变量m_Age;
    //那么这样就会逻辑出错!达不到你写这个函数的目的了!
	return *this;
}
p2.add_Age(3).add_Age(3).add_Age(3).showInfo();
//result: 20 0 lzf
//可见我上述所说是right的!

Person& Person::add_Age(int m_Age) {
	this->m_Age += m_Age;//当你加上this->来调用m_Age这个成员变量时
    //就不会出现所传入的参数与成员变量同名的case了
	return *this;
}
p2.add_Age(3).add_Age(3).add_Age(3).showInfo();
//result: 29  0 lzf
//可见我上述所说是right的!

        虽然我的解释稍微有点啰嗦,但我相信,通过以上的详解,你已经学会了啥是返回对象本身的引用以及this指针这2个小但又很重要的知识点了。

五、static(静态)成员:

                (是个重要的概念,也是我个人经常遗忘的概念,务必要认真学习并总结!)

        首先,我带你回顾一下静态变量的概念,静态变量分为局部和全局2种。

(注意:你定义静态变量时,若你不赋初值,则编译器会默认给这个静态变量赋初值为0,且静态变量都是存储在静态存储区的。)

static int abc;//编译器默认给abc赋初值为0
cout << abc << endl;//    0

        ①局部静态static变量只会初始化一次,然后随着外部调用该函数对其更新,进而保留最新的值,也即:只会保留上一次调用该函数时,的这个变量的值。

(所谓的局部,其实就是因为该变量是在函数体内,因此是局部变量)

(所谓的静态,其是加了static关键字就是静态的了,静态就是只初始化一次的意思)

void func() {
	int static abc = 1;//局部静态变量 ==> static int abc = 1;
	abc++;
	cout << abc << "\t";
}
//main.cpp中
for (int i = 0; i < 5; i++) { func(); }
//result: 2 3 4 5 6
一开始是1,然后++使得abc变为2,然后输出2
然后上一次调用该函数的abc值为2,因此用2++使得abc变成3,然后输出3
然后上一次调用该函数的abc值为3,因此用3++使得abc变成4,然后输出4
然后上一次调用该函数的abc值为4,因此用4++使得abc变成5,然后输出5
然后上一次调用该函数的abc值为5,因此用5++使得abc变成6,然后输出6
so综上,result:2 3 4 5 6

        ②全局静态static变量:在一个.cpp源文件的 任何函数之外 定义的静态static变量 并且作用域限定在定义该全局静态变量的.cpp源文件中的变量就是全局静态static变量。

(别的.cpp源文件就算用extern关键字也无法访问到该全局静态变量。)

//在 任何函数之外 定义的静态static变量 并且作用域只在本.cpp源文件中 那它就是全局静态static变量
static int g_abc = 998;// ==> int static g_abc = 998;


//在 任何函数之外 定义的变量就是全局变量(可以给别的.cpp源文件通过extern关键字去使用)
int g_a = 1998;

在这个例子中,

别的.cpp源文件就算用extern int g_abc;也无法访问到该全局静态变量g_abc。但是,

别的.cpp源文件可以用extern int g_a;来访问到该全局变量g_a;

        以上就是我们在C语言中学到的关于static关键字的内容。那static在C++的类中有啥功能呢?让我们一起来看看把!下面我们不妨通过代码来理解这个“新”的知识点:

//将上述定义的Person类的showInfo()函数修改为:
void Person::showInfo(){
	cout << "m_Age = " << m_Age << "\t";
	cout << "m_Name = " << this->m_Name.getName() << endl;
}
//并在main.cpp中去创建2个Person类的对象
Person p1(21, person_Name("lzf"));
p1.showInfo();
// m_Age = 21  m_Name = lzf 21属于对象p1的m_Age成员变量 "lzf"属于对象p1的m_Name成员变量
Person p2(22, person_Name("lyf"));
p2.showInfo();
// m_Age = 22  m_Name = lyf 22属于对象p2的m_Age成员变量 "lyf"属于对象p2的m_Name成员变量

从中可见,每个对象都各有其独一份儿的数据,p1的年龄和p2的年龄是互不相干的,p1的姓名和p2的姓名也是互不相干的。这2对象所存储的数据占据的是不同的内存地址。

通过在VS2019上截取断点查看地址:(可以验证我上面的讲解)

 那么,肯定有同学会问,是否存在一种,只属于整个类的,公有的,对象呢?

答:存在的,那就是static静态成员(本小节的重点)

        static静态成员:就是用static修饰的成员变量or成员函数,我自己对于静态成员(成员变量or成员函数)这个知识点的理解就是用一个词儿概括:

        跨对象(静态成员是跨越对象的,是共享的,是公共的!跨越类中的all对象而存在的)

        解释:普通的成员对于每一个对象都有其独一份二的内存地址副本,而静态的成员的内存地址在类中只会有一份副本,或者说都共享同一份副本!

        我们也可以理解为,普通的成员函数和成员变量都是动态的,因为对于每一个对象都会有独一份儿他们独自的成员函数和成员变量。而静态的成员函数和成员变量对于all的对象而言都是静态的不动的不变的,你任何一个对象对我们的静态成员do事情,会影响到all的对象。

特点:

        ①静态static的成员变量 不属于某个具体对象,而属于整个类(独一份儿)。一旦我们在某个对象中修改了该静态成员变量的话,我们在其他对象中就可看到更新之后的这个静态成员变量了。并且,static类型的成员变量只可以:

1)类内声明(声明时要加static关键字,不分配内存)

2)类外初始化(或者说定义,此时不能再加static关键字了,分配内存)

格式:static变量的类型 属于的类的名字::变量名 = 初始值;)

        静态成员变量的访问方式:

1)可通过类名::静态成员变量名的方式对其进行访问or修改

2)用任何一个对象的名字.静态成员变量名的方式对其进行访问or修改

(因为静态成员变量在类内时,编译器不会为其分配内存,所以我们不能给其初始化,没内存空间你初始个毛化呢对吧?你给它的初始值往哪儿存放呢对吧?)

请看以下代码:(延用三、mutable中的Person类写代码)

//因为在类内我已经定义了一个静态的成员变量m_girlFriendNums
public:
    //声明静态成员变量(不分配内存)
	static int m_girlFriendNums;//这只是类内声明静态成员m_girlFriendNums而已
    //因为static类型的成员变量只可以在类内声明,类外初始化

//so在类外初始化静态成员变量m_girlFriendNums
    //定义or初始化静态成员变量(分配内存),若此时你不给它初始值,则编译器会默认给0
int Person::m_girlFriendNums = 8;//给m_girlFriendNums一个初始值8(类外初始化)

//再在main.cpp中写如下代码:
int main(void) {
	Person p1(21, person_Name("lzf"));
	p1.m_girlFriendNums += 1;//通过某个具体对象来修改静态成员变量时,那么这份公有的数据就会被修改
	p1.showInfo();
	Person p2(22, person_Name("lyf"));
	p2.showInfo();
    //你还可与通过类名::静态成员变量名的方式去访问or修改它!
    Person::m_girlFriendNums = 7;
    p1.showInfo();
    p2.showInfo();
	return 0;
}

 运行结果:

         ②静态static的成员函数 也不属于某个具体对象,而属于整个类(独一份儿)。一旦我们在某个对象中修改了该静态成员函数的话,我们在其他对象中就可看到更新之后的这个静态成员函数了。且,静态成员函数只能访问/修改静态的成员变量,而不能访问/修改普通类型的成员变量。

解释:也就是说静态成员函数不可 访问/修改 那些不跨越对象的成员变量(即:对于不同的对象都有其相互独立的内存来存储相应成员变量),so也就是不可与访问那些非static静态成员变量,你可以这么去记忆:static静态成员函数只能修改static静态成员变量而不能修改非static的成员变量

(记忆这一条就足够了!!!)

static类型的成员函数

1)类内(.h头文件内)声明(声明时要加static关键字)

2)类外(.cpp源文件内)实现(或者说定义,此时不能在函数返回值类型前再加static关键字了)

格式:函数返回值类型 属于的类的名字::函数名(对应声明的形参表){......}

        静态成员函数的访问方式:

1)可通过类名::静态成员函数名(对应的参数表)的方式对其进行访问or修改

2)用任何一个对象的名字.静态成员函数名(对应的参数表)的方式对其进行访问or修改

请看以下代码:

//在类内定义一个静态的成员函数static void reviseAge(int age);
//Person.h加上:
public:
    static void reviseAge(int age);
//在Person.cpp加上:
void  Person::reviseAge(int age) {
	m_girlFriendNums = age;
    //这个静态成员函数会将static静态成员变量m_girlFriendNums这个独一份儿的值修改为age
    //m_Age = 0;//错误!因为静态成员函数只能访问静态的成员变量
    //m_Name = person_Name("tjr");//错误!因为静态成员函数只能访问静态的成员变量
}
//再在main.cpp中写如下代码:
int main(void) {
    Person p1(21, person_Name("lzf"));
	p1.m_girlFriendNums += 1;
	p1.reviseAge(111);
	p1.showInfo();
	Person p2(22, person_Name("lyf"));
	p1.reviseAge(222);
	p2.showInfo();
	Person::m_girlFriendNums = 7;
	p1.showInfo();
	p2.showInfo();
	Person::reviseAge(1);
	p1.showInfo();
	p2.showInfo();
	return 0;
}

 运行结果:

        最后,通过几行代码再一次验证一下静态成员变量以及静态成员函数在类中是否真的是独一份儿,只有一份内存数据,是跨越all对象的,公有的,公共的呢?

再上述Person类的代码基础上,请看以下代码:

//在Person.cpp中给静态成员函数reviseAge()加一行输出test代码:
void  Person::reviseAge(int age) {
	m_girlFriendNums = age;
	cout << "本次调用时的静态成员变量 m_girlFriendNums = " << m_girlFriendNums << endl;
}
//在main.cpp中写如下代码:
int main(void) {
	Person p1(21, person_Name("lzf"));
	Person p2(22, person_Name("lyf"));
    //静态成员变量m_girlFriendNums的默认值我们set为8 
	cout <<"Person::m_girlFriendNums = "<< Person::m_girlFriendNums <<
		" &Person::m_girlFriendNums = "<< &Person::m_girlFriendNums <<endl;
	cout <<"p1.m_girlFriendNums = "<< p1.m_girlFriendNums << 
		" &p1.m_girlFriendNums = " << &p1.m_girlFriendNums << endl;
	cout <<"p2.m_girlFriendNums = "<< p2.m_girlFriendNums << 
		" &p2.m_girlFriendNums = " << &p2.m_girlFriendNums << endl;
	Person::reviseAge(0);
	p1.reviseAge(1);
	p2.reviseAge(2);
    return 0;
}

运行结果:

        

        好,那么以上就是这一重要的3.3小节我所回顾的内容的学习笔记,希望你能读懂并且消化完,也希望自己能牢记这些小小的细节知识点,加油吧,我们都在coding的路上~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fanfan21ya

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值