【④C++ | 特殊成员】静态成员 | 面向对象模型(this) | 常成员 | 友元 | 成员指针

在这里插入图片描述

前言

✨欢迎来到小KC++专栏,本节将为大家带来C++特殊成员—静态成员 | 面向对象模型(this) | 常成员 | 友元 | 成员指针 的分享


1、静态(static)成员

静态成员分为静态成员变量静态成员函数,在成员变量和成员函数前面加上关键字static即可!

静态成员提供了一个同类对象间数据共享的机制,一个类中可以有一个或多个静态成员,所有的对象都共享这些静态成员,都可以使用它。

静态成员变量

static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。

静态成员变量必须在类的内部申明,在类的外部定义。(C++17有了新写法,可以直接内联到类的内部)

class XXX
{
    static int s_cnt;	//类的内部声明
    inline static int s_num;	//C++17 可以直接内联,就不需要在类的外部定义了
};
int XXX::s_cnt = 0;		//类的外部定义

静态成员变量既可以通过对象名访问,也可以通过类名访问,但要遵循 private、protected 和 public 关键字的访问权限限制。

在这里插入图片描述

静态成员函数

在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。普通成员函数可以访问所有成员(静态和非静态都可以),静态成员函数只能访问静态成员。

比如说,我们要通过函数获取学生总人数(m_classSize),有两种方法

  • 定义普通成员函数:可以访问静态成员,但是这个函数只对静态成员进行操作,加上 static 语义更加明确。
  • 定义静态成员函数:在函数前面加上static,可以声明为静态函数

静态成员函数调用方式,有两种:

  • 和普通成员函数一样,通过对象去调用 maye->classSize();
  • 不定义对象,直接通过<类名::函数名>去调用 Student::classSize();

思考:为什么静态成员函数不能调用类的非静态成员?

在C++中,静态成员函数是属于整个类而不是类的某个对象的。它们不依赖于任何特定的类对象,因此无法访问类的非静态成员。

静态成员函数可以直接通过类名来调用,而无需创建类的对象。由于没有对象实例,静态成员函数无法访问属于类对象的非静态成员变量或非静态成员函数。这是因为非静态成员是与类的对象相关联的,需要通过类的对象才能访问和操作。

另外,静态成员函数在其作用域中是独立的,不会继承或共享非静态成员。因此,它们无法直接访问非静态成员。

如果需要在静态成员函数中使用非静态成员,可以通过以下方式之一来实现:

  1. 创建类的对象,并使用对象来访问非静态成员。
  2. 将非静态成员声明为静态成员,以便在静态成员函数中直接访问。

需要注意的是,静态成员函数主要用于执行与类相关的操作,而不依赖于特定的对象状态。静态成员函数可以直接通过类名进行访问,因此它们通常用于执行一些与类关联的全局操作或提供类级别的服务。

2、面向对象模型

C++对象模型可以概括为以下两部分:

  • 语言中直接支持面向对象程序设计的部分, 主要设计如构造函数、析构函数、虚函数、继承、多态等等。
  • 对于各种支持的底层实现机制

C++中的类从面向对象理论出发,将变量(属性)和函数(方法)集中定义在一起,用于描述现实世界中的类。从计算机的角度,程序依然由数据段和代码段构成。

C++编译器如何完成面向对象理论到计算机程序的转化?

换句话:C++编译器是如何管理类、对象、类和对象之间的关系

具体的说:具体对象调用类中的方法,那c++编译器是如何区分,是哪个个具体的对象,调用这个方法那?

思考下面程序运行的结果?

class C1
{
public:
    int i;
    int j;
    int k;
    static int number;
};

class C2
{
public:
    int i;
    int j;
    int k;
    int getK(){return k;}
    void setK(int nk){i = nk;}
};

void test()
{
    cout<<"c1:%d "<<sizeof(C1)<<endl;
    cout<<"c2:%d "<<sizeof(C2)<<endl;
}

output:

c1:%d 12
c2:%d 12

通过这个案例,我们可以猜测出C++类中的成员变量和成员函数是分开存储的,成员函数并不占对象的空间。

  • 成员变量
    • 普通成员变量:存储在对象中,与struct对象有相同的内存布局和字节对齐方式
    • 静态成员变量:存储在静态变量区(全局区)
  • 成员函数
    • 存储在代码段中

但是类限定了静态成员变量和成员函数的作用域,只有通过类或对象才能访问到!

问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?

换句话说:int getK() { return k; },代码是如何区分,具体obj1、obj2、obj3对象的k值?

普通成员函数的内部处理

左边为C++代码,如何把C++代码改为C语言代码呢?

在这里插入图片描述

其实把C++的类改为C语言,只需要用结构体+函数,然后通过传对象的指针,在函数里面访问即可!(右边的代码)

pthis对象指针

把C++类改为C语言形式之后,每个普通成员函数的第一个参数是指向当前操作的对象的pthis指针。这个指针其实还不太安全,因为我们可以在函数改变pthis指针的指向,虽然不能影响原来对象的值,但可能会导致结果不正确。

在这里插入图片描述

在上面的代码中,pthis在Test_Init函数中被改变,然后给指向改变之后的pthis的m_t赋值,可以看到被修改之后,即使初始化了成员,输出也是乱码,非常的不安全!

那么有没有什么办法可以让pthis指针在函数里面不能被修改呢?

  • 可以给pthis指针加上const属性,让它不能被改变指向

在这里插入图片描述

  • 但是在C++中,对pthis指针做了隐藏,我们看不到,所以编译器帮我们增加了const属性

  • 我们在这里研究的pthis指针其实在C++中叫做this指针~

this指针

this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,在类的内部可以通过它可以访问当前对象的所有成员。

所谓当前对象,是指正在使用的对象。例如对于t.getT();t就是当前 对象,this 就指向 t。

在这里插入图片描述

3、常(const)成员

在类中,如果你不希望某些数据被修改,可以使用const关键字加以限定。const 可以用来修饰成员变量和成员函数。

const成员变量

const 成员变量的用法和普通 const 变量的用法相似,只需要在声明时加上 const 关键字。初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表。

在这里插入图片描述

必须通过构造函数的初始化列表初始化,初始化之后不允许赋值。

在这里插入图片描述

const成员函数

const 成员函数可以使用类中的所有成员变量,但是不能在函数里面修改它们的值,这种措施主要还是为了保护数据而设置的。const 成员函数也称为常成员函数。

那const加在函数的哪个地方呢?其实const写在哪里不重要,重要的是修饰的谁?

  • 通过测试发现写在函数返回值前面和后面的都是修饰的返回值

  • 而放在函数原型后面的是修饰的this指针所指向的对象不能修改,这个函数就叫做const成员函数

在这里插入图片描述

mutable

mutable的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。

在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。

我们知道,如果类的成员函数不会改变对象的状态,那么这个成员函数一般会声明成const的。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。

注意

  • 常成员函数可以访问任何成员

  • 常对象只能访问常函数

4、友元(friend)

私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口(成员函数)间接地进行。这固然能够带来数据隐藏的好处,利于将来程序的扩充,但也会增加程序书写的麻烦

所以C++引入友元的概念,使得被定义为友元的函数即使在外部,也能访问类中的私有成员。

友元函数

友元函数是指某些虽然不是类成员函数却能够访问类的所有成员的函数。就像好朋友一样,有钱一起花~

  • 全局函数作为友元函数
class GirlFriend
{
    friend void stage(GirlFriend& girl);
private:
    int data;
public:
    GirlFriend(int data)
        :data(data)
    {}
};

void stage(GirlFriend& girl)
{
    GirlFriend t(33);
    cout << "data: " << girl.data<<" " << t.data;
}

int main()
{
    GirlFriend g(2);
    stage(g);
    return 0;
}

我们在GirlFriend类中把stage函数声明为了友元函数,这样子就可以在stage函数中访问GirlFriend类的私有成员了~

  • 成员函数作为友元函数
class GirlFriend;       //必须提前声明
class BoyFriend
{
public:
    BoyFriend() = default;
    void kissGirlFriend(GirlFriend* girlFriend);
};

class GirlFriend
{
    friend void stage(GirlFriend& girl);
    friend void BoyFriend::kissGirlFriend(GirlFriend* girlFriend);
private:
    int data;
public:
    GirlFriend(int data)
        :data(data)
    {}
};

void BoyFriend::kissGirlFriend(GirlFriend* girlFriend)
{
    cout << "kiss kiss " << girlFriend->data << endl;
}

int main()
{
    GirlFriend g(2);
    BoyFriend boy;
    boy.kissGirlFriend(&g);
    return 0;
}

一个类的成员函数作为另一个类的友元函数的时候,这个成员函数必须在类外实现,而且是必须在作为友元的类之后实现。

  • 注意点
    • 友元的概念是针对类外部函数(包括全局函数和其他类的成员函数)而言的,类自身的成员函数可以自由访问自己类中的成员。
    • 不能把别的类的私有函数定义为友元。
    • 一个函数可以被多个类声明为友元函数,这样就可以访问多个类中的 private 成员

友元类

class GirlFriend
{
    friend class BoyFriend;
private:
    int data;
public:
    GirlFriend(int data)
        :data(data)
    {}
};

class BoyFriend
{
private:
public:
    void bedExercise(GirlFriend* girl)
    {
        cout << __FUNCTION__ << " " << girl->data;
    }
};

int main()
{
    GirlFriend g(234);
    BoyFriend().bedExercise(&g);
    return 0;
}

在GirlFriend类中,把BoyFriend类声明为了友元类,此时在BoyFriend类中,就可以随心所欲的访问GirlFriend类的私有成员了!

注意

友元函数有优点也有缺点,用好了是神器,用坏了是毁灭!

**优点:**能够提高效率,表达简单、清晰

缺点: 友元函数破环了类的封装性,尽量使用成员函数,除非不得已的情况下才使用友元函数。

友元的实际应用其实很有限,用的不多。

5、成员指针

实例代码:以下两个知识点我们从具体的代码进行分析

#include<iostream>
using namespace std;
void ktest() {
	cout << __FUNCTION__ << endl;
}
class Test {
public:
	void test();
	static void static_test();

	int age = 20;
	static int static_age;
};
void Test::test() {
	cout << __FUNCTION__ << endl;
}
void Test::static_test() {
	cout << __FUNCTION__ << endl;
}
int Test::static_age = 30;

在这里插入图片描述

成员函数指针

1、全局函数指针

	void (*pktest)() = &ktest;   
	pktest();                 

可以清晰的知道 ktest == &ktest; pktest() == (*pktest)();那我们看看下面的成员函数指针也是否如此

非静态成员函数必须取地址而且前面要加上类的限定,调用要通过对象

	void (Test:: * ptest)() = &Test::test;  
	Test a;       
	(a.*ptest)();

静态成员函数这里稍微不同的是,定义的时候前面不用类名进行限定,调用直接调用,不能通过对象调用

	void ( * pstatic_test)() = &Test::static_test;
	pstatic_test();

成员变量指针

具体的类比成员函数指针
非静态成员变量

	int Test:: * k = &Test::age;
	cout << a.*k << endl;

静态成员变量

	int* kk = &Test::static_age;
	cout << *kk << endl;

总结

这些概念和特殊成员提供了更高级别的功能和灵活性,有助于更好地设计和实现面向对象的程序。理解这些概念对于理解C++的面向对象编程非常重要。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

xiaok-cpp

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

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

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

打赏作者

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

抵扣说明:

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

余额充值