本专栏目的
- 更新C/C++的基础语法,包括C++的一些新特性
前言
- 通过前面几节,我们介绍了C++的类与对象、构造与析构函数、拷贝等相关知识,这一篇将详细介绍了C++的成员变量相关的知识点与扩展
- C语言后面也会继续更新知识点,如内联汇编;
- 本人现在正在写一个C语言的图书管理系统,1000多行代码,包含之前所学的所有知识点,包括链表和顺序表等数据结构,请大家耐心等待!!预计国庆前写完更新。
文章目录
- 类中数据与函数储存
- 静态(static)成员
- 静态成员变量
- 静态成员函数
- 普通成员函数的内部处理
- pthis对象指针与this指针
- 常(const)成员
- const成员变量
- const成员函数
- mutable
- 注意
- 友元(了解即可)
- 友元函数
- 友元类
- 注意
- 成员指针
- 成员变量指针
- 非静态成员变量指针
- 静态成员变量指针
- 成员函数指针(类)
- 非静态成员函数指针
类中数据与函数储存
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;
}
输出结果:
c1:%d 12
c2:%d 12
原因:
- 成员变量
- 普通成员变量:存储在对象中(对象存储位置由创建位置决定),与struct对象有相同的内存布局和字节对齐方式(尤其要注意对其方式)
- 静态成员变量:存储在静态变量区(全局区),这个下一节也将会详细讲解
- 成员函数
- 存储在代码段中,而对象储存位置由创建的位置决定。
问题出来了:很多对象共用一块代码?代码是如何区分具体对象的那?
换句话说:int getK() { return k; },代码是如何区分,具体obj1、obj2、obj3对象的k值?
静态(static)成员
C++是对类的抽象,故在C++中类中静态成员分为静态成员变量
和静态成员函数
,创建静态成员变量
和静态成员函数
只需要用关键之static
静态成员在内存中存储在全局区,声明周期是从程序开始到结束,故其他成员可以贡献其资源。
静态成员变量
-
static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放,也就是他不占用类的内存空间(类的生命周期是创建到类的销毁,而销毁可能是超出作用域,也可能手动释放)
-
静态成员变量必须在类的内部申明,在类的外部定义,除非是使用内联,因为静态成员变量不属于对象,他在全局区,在类或者结构体中只是声明 ,后面必须要定义
class W
{static int s_cnt; //类的内部声明inline static int s_num; //C++17 可以直接内联,就不需要在类的外部定义了
};
int W::s_cnt = 0; //类的外部定义
- 访问权限:既可以使用对象访问,也可以使用类名访问
静态成员函数
- 在类中,static 除了可以声明静态成员变量,还可以声明静态成员函数。
- 使用权限:普通成员函数可以访问所有成员(静态和非静态都可以),静态成员函数只能访问静态成员。
比如说,我们要通过函数获取学生总人数(m_classSize),有两种方法:
- 定义普通成员函数:可以访问静态成员,但是这个函数只对静态成员进行操作,加上 static 语义更加明确。
- 定义静态成员函数:在函数前面加上static,可以声明为静态函数
静态成员函数调用方式,有两种:
-
和普通成员函数一样,通过对象去调用:
-
Student stu; stu.classSize();
-
不定义对象,直接通过类名::函数名去调用
-
Student::classSize();
思考:为什么静态成员函数不能调用类的非静态成员?
在类的定义中,变量内存分配和函数是不同的,类的内存大小=类的成员变量内存大小,而成员函数是在创建对象的时候要调用的时候创建的,这样有利于节约内存,而静态成员函数存储在全局区,一定义就创建出来了,如果在静态成员函数中调用类的成员函数的话,就会发现那个成员函数根本不存在,总的来说就是:
- 上下文环境不同
- this指向不同
普通成员函数的内部处理
左边为C++代码,右边为C语言代码
其实把C++的类改为C语言,只需要用结构体+函数,然后通过传对象的指针,在函数里面访问即可!(右边的代码)
pthis对象指针与this指针
通过C语言的方式,对成员变量进行修改、赋值…………操作,用C风格方式,可能的采用指针的方法,但是这样也有一个问题,如下:
上面问题就是,pthis这个指针指向修改了,并不是指向原有的位置。
那么有没有什么办法可以让pthis指针在函数里面不能被修改呢?
- 可以给pthis指针加上const属性,让它不能被改变指向
而这个加上const的pthis指针,就是C++的this指针。
注意:
- this 是 C++ 中的一个关键字,也是一个 const 指针,它指向当前对象,在类的内部可以通过它可以访问当前对象的**所有成员*,如下是他的使用操作
class Stu
{
public:Stu(int age, char* name){this->age = age;}
private:int age;
}
- 以上发现,如果函数中形参和类的属性名相同,可以通过this解决。
常(const)成员
在类中,如果你不希望某些数据被修改,可以使用const
关键字加以限定。const 可以用来修饰成员变量和成员函数的关键字。
const成员变量
const 成员变量的用法和普通 const 变量的用法相似,只需要在声明时加上 const 关键字。
- 初始化 const 成员变量只有一种方法,就是通过构造函数的初始化列表。
class Test
{
public:Test() {}; // 报错:error C2789:“Test::a”:必须初始化常量限定类型的对象const int m_a;
}
- 必须通过构造函数的初始化列表初始化,初始化之后不允许赋值。(但是初始化列表可以)
class Test
{
public:Test(int a): m_a(8){m_a = 2; // error C2166:左俏指定 const 对象}const int m_a;
}// 注意:这种方法不行
class Test
{
public:Test(int a){m_a = a; // 会报错}
public:const int m_a;
};
const成员函数
const 成员函数可以使用类中的所有成员变量,但是不能在函数里面修改它们的值,通过const 成员函数也称为常成员函数。
但要注意,类中的常成员函数中的const
放的位置有点独特,放在函数后面,如下:
class Text
{
public:void fix() const // 放到这里{m_age = 2; // 会报错}
private:int m_age;
}
mutable
在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中,如上面程序可以做修改:
class Text
{
public:void fix() const // 放到这里{m_age = 2; // 这样就不会报错了}
private:mutable int m_age;
}
注意
- 常成员函数可以访问任何成员
- 常对象只能访问常函数,这个很容易忽略,但是编译器编译的时候,我们也很容易发现错误更改。
友元(了解即可)
在C++中,私有成员只能在类的成员函数内部访问,如果想在别处访问对象的私有成员,只能通过类提供的接口间接地进行,所以C++引入友元的概念,使得被定义为友元的函数即使在外部,也能访问类中的私有成员。
友元函数
- 全局函数作为友元函数
class GirlFriend
{//在public之前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 BoyFriend;
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
类的私有成员了!
注意
友元函数有优点也有缺点,用好了是神器,用坏了是毁灭!
**优点:**能够提高效率,表达简单、清晰
缺点: 友元函数破环了类的封装性,尽量使用成员函数,除非不得已的情况下才使用友元函数
建议:常中用的也不多,用到忘了在查也行
成员指针
下面内容,了解即可,需要实际情况学习,忘了现查也行。
成员变量指针
非静态成员变量指针
定义成员变量指针:
int A::*p = &A::num; // 注意:*p是定义的,在A类中没用被定义
通过成员变量指针访问成员变量:
A a(22);
cout<<" "<<a.*p; // ** 必须 **通过对象去访问
静态成员变量指针
定义静态成员变量指针:
int *p = &A::static_number;
可以通过对象访问
A a(22);
cout<<" "<<a.*p;
// 也可以,直接输出
cout << *p << endl;
成员函数指针(类)
非静态成员函数指针
定义成员函数指针:
void (A:: * pshow)() = &A::show; //对于成员函数来说必须取地址
//A:: show() 这是类的真正原型,在类中只是省略了
通过成员函数指针调用成员函数
//在类外调用 , 必须通过对象调用
(a.*pshow)(); //必须先实例化对象
//在类内调用
(this->*pshow)();
静态成员函数指针
定义静态成员函数指针:(存在全局区里)
void (* pstatic_show)() = &A::static_show;
不可以通过对象访问
cout<<" "<<(*pstatic_show());
也可以直接访问
cout<<" "<<pstatic_show();
- 定义静态成员指针时候,左边都不加 对象:: ,只有定义非静态成员才会加 (A:: *show)() 。但是右边一样,都要加&