【类与对象(上)】C++封装之美:类与this指针解析
类
类的本质是封装 ,相比c语言,c语言的数据和方法都是分离的,c++把数据和方法都放到了类里面
类的定义格式:
class Stack//定义一个栈
{
private:void Init(int capacity=4){_array=(int*)malloc(sizeof(int)*capacity);assert(_array);_capacity=capacity;_top=0;}
public: int*_array;size_t _capacity;size_t _top;
}
class为定义类的关键字,Stack为类的名字,{}中为类的主体
类中的内容:类的成员
类中变量:成员变量,一般为了却分成员变量,会在变量名前加一个特殊标识符
类中可以定义函数,称为成员函数
定义在类里面的成员函数默认为内敛函数
内联函数:
内联函数的概念
内联函数(Inline Function)是C++中一种优化函数调用的机制,通过将函数体直接插入调用处来避免函数调用的开销(如压栈、跳转、返回等)。适用于短小且频繁调用的函数。
内联函数的定义
在函数声明或定义前添加 inline
关键字即可将其标记为内联函数:
inline int add(int a, int b) {return a + b;
}
编译器会根据实际情况决定是否真正内联(如递归函数、复杂函数可能被忽略)。
内联函数的优势
- 减少开销:消除函数调用时的额外指令(如参数传递、栈帧操作)。
- 提升性能:适合高频调用的简单函数(如短循环内的操作)。
内联函数的限制
- 体积膨胀:代码被多次复制可能导致可执行文件变大。
- 编译器自主性:
inline
仅是建议,编译器可能拒绝复杂函数的内联请求。 - 调试困难:内联后的代码难以设置断点。
内联函数与宏的区别
- 类型安全:内联函数遵循C++类型检查,宏仅是文本替换。
- 调试支持:内联函数可调试,宏在预处理阶段已展开。
- 副作用:宏可能因多次参数求值导致意外行为(如
#define SQUARE(x) x*x
调用SQUARE(++x)
会出错)。
内联函数的使用场景
- 短小函数:如简单的数学运算、getter/setter。
- 高频调用:循环内的轻量级操作。
- 替代宏:需要类型安全且避免副作用的场景。
示例代码
#include <iostream>
using namespace std;inline int max(int x, int y) {return (x > y) ? x : y;
}int main() {cout << max(10, 20); // 可能被替换为 cout << (10 > 20 ? 10 : 20);return 0;
}
注意事项
- 避免内联复杂函数(如含循环、递归)。
- 头文件中定义内联函数时需确保函数体可见(通常在头文件中实现)。
- 现代编译器(如GCC、Clang)能自动内联简单函数,无需显式指定
inline
。
类与struct的区别:
类中内容称为类的成员,类中的变量称为类的成员变量或者类的属性
类中可以定义函数,类中的函数称为类的方法或者类的成员函数
用栈来举例,结构体中放的只是我们需要的数据,方法是单独写的,但是在类中我们把这些都放在了class的主体中了
类可以直接使用类名,不需要使用typedef来重命名。(类名就是类型)
类的访问权限:
public,后面的内容为共有
private\ protected,后面内容为私有
class Stack
{piblic:void Init(int n=4)//给栈初始化{....}void push(int x)//入栈{...}private:int *array;size_t capacity;size_t top;
}
一般成员函数是公有的,成员变量是私有的。
访问权限的范围,比如上面public,从“:”开始,一直到private ,也就是从自身到下一个访问限定符,如果只有一个访问限定符,那么就是直接到“}”结束。
在class中,如果没给出访问限定符,那么默认是私有
在类中,不管是私有还是公有,都是能访问的
在c++中,struct也可用来定义类,当struct中没有给出访问限定符时,默认是公有
c++struct的用法兼容c
访问限定符
- C++⼀种实现封装的方式,用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
- public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到即类结束。
- class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
- ⼀般成员变量都会被限制为private/protected,需要给别⼈使用的成员函数会放为public。
类域:
域的作用是影响编译器的查找,做到名字的隔离
会影响到编译器的查找,如果不指定类域,编译器在编译时将不会访问类域
类的实例化:
类是没有被分配空间的,只有类实例化出的对象才会被分配空间
类就像一个房子的设计图,告诉了我们房子的样式,但不能住人,换类来说就是不能存数据,只有类实例化出的对象才能存数据
一个类可以实例化多个对象
class Data
{private://这是声明int _year;int _month;int _day;
};int main()
{//这是类定义一个对象,也叫类实例化出对象Data d1;Data d2;
}
//类与对象的关系:1对多,一个类可以实例化出多个对象。
对象大小
类实例化出的对象也是有大小的。
类实例化出的对象只会在对象中存储类中的成员变量,不会存储成员函数。因为存储成员函数是没必要的,我们每次调用的成员函数都是同一个,他们唯一的区别就是在调用函数时传入的数据不同。如果我们非要在对象中存储成员函数,那么存储的是成员函数的指针,我们每实例化一个对象,就得存储一次指针,存储100个,就得存储100个指针,但是这些个空间是完全没必要的,是一种空间的浪费。
计算对象的大小时,也存在内存对齐(与结构体一样):
第⼀个成员在与结构体偏移量为0的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的⼀个对齐数与该成员大小的较小值。
VS中默认的对齐数为8
结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
class A
{
public:void Print(){cout << _ch << endl;}
private:char _ch;int _i;
};
计算A实例化对象的大小
A中,存在两个成员变量 ch 与i
_ch是char类型,又是第一个元素,那么它放在偏移量为0的地址出,占1字节
class B
{
public:void Print(){//...}
};
计算B实例化对象的大小
因为实例化对象中,只会存储成员变量,该代码中没有成员变量,所以实例化出的对象大小为1字节,着一个字节是用来占位的,表示对象存在过,无其他意义。
class C
{};
计算C实例化对象的大小
这段代码与上述的B一样。
this 指针
在d1 、d2调用方法时,编译器是怎么知道方法应该访问d1还是d2?
编译器编译后,类的成员函数都会默认在形参的第一位置,增加一个当前类类型的指针叫做this。
类的成员函数访问成员变量,本质是通过this指针来访问的。
c++规定,不能在形参实参的位置显示写this指针,但可以在函数体内显示的使用this指针。
class Data
{
public:void Init(int year,int month,int day)//Init(Data*this.....){_year = year;_month = month;_day = day;}void Print()//Print(Data*this){//cout << this->_year << '/' <<this-> _month << '/' << this->_day << endl;cout << _year << '/' << _month << '/' << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Data d1;Data d2;//d1.Init(&d1,2024,2,3);//d2.Init(&d2,2024,3,1);d1.Init(2024,2,3);d2.Init(2024,3,1);//d1.Print(&d1)//d2.Print(&d2)d1.Print();d2.Print();return 0;
}
如果我们要让this指针指向对象的值无法被修改,我们可以用const来修饰this。
class Test
{
public:Test(){a = 10;}int get_val()const//{this->a = 20;//这里编译器会报错,因为this指针被const修饰,无法通过this指针return a;}private:int a;
};int main()
{Test t1;int ret = t1.get_val();cout << ret << endl;return 0;
}
c++与c语言实现stack对比
面向对象三大特性:封装、继承、多态
c++中数据与方法都放在了类里面,通过访问限定符进行了限制,不能再随意的通过对象对数据进行修改。避免出现乱访问修改的问题。
c++有一些相对方便的语法,比如缺省参数。
- C++中数据和函数都放到了类里面,通过访问限定符进行了限制,不能再随意通过对象直接修改数据,这是C++封装的⼀种体现,这个是最重要的变化。这里的封装的本质是⼀种更严格规范的管理,避免出现乱访问修改的问题。当然封装不仅仅是这样的,我们后面还需要不断的去学习。
- C++中有⼀些相对方便的语法,比如Init给的缺省参数会方便很多,成员函数每次不需要传对象地址,因为this指针隐含的传递了,方便了很多,使用类型不再需要typedef用类名就很方便
- 在我们这个C++入门阶段实现的Stack看起来变了很多,但是实质上变化不大。等着我们后面看STL中的用适配器实现的Stack,⼤家再感受C++的魅力。
C实现Stack代码
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;void STInit(ST* ps)
{assert(ps);ps->a = NULL;ps->top = 0;ps->capacity = 0;
}void STDestroy(ST* ps)
{assert(ps);free(ps->a);ps->a = NULL;ps->top = ps->capacity = 0;
}void STPush(ST* ps, STDataType x)
{assert(ps);// 满了, 扩容if (ps->top == ps->capacity){int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;
}bool STEmpty(ST* ps)
{assert(ps);return ps->top == 0;
}void STPop(ST* ps)
{assert(ps);assert(!STEmpty(ps));ps->top--;
}STDataType STTop(ST* ps)
{assert(ps);assert(!STEmpty(ps));return ps->a[ps->top - 1];
}int STSize(ST* ps)
{assert(ps);return ps->top;
}int main()
{ST s;STInit(&s);STPush(&s, 1);STPush(&s, 2);STPush(&s, 3);STPush(&s, 4);while (!STEmpty(&s)){printf("%d\n", STTop(&s));STPop(&s);}STDestroy(&s);return 0;
}
C++实现Stack代码
#include<iostream>
using namespace std;typedef int STDataType;
class Stack
{public:// 成员函数void Init(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (nullptr == _a){perror("malloc申请空间失败");return;}_capacity = n;_top = 0;}void Push(STDataType x)
{if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)realloc(_a, newcapacity *sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}_a = tmp;_capacity = newcapacity;}_a[_top++] = x;
}void Pop()
{assert(_top > 0);--_top;
}bool Empty()
{return _top == 0;
}int Top()
{assert(_top > 0);return _a[_top - 1];
}void Destroy()
{free(_a);_a = nullptr;_top = _capacity = 0;
}
private:// 成员变量STDataType* _a;size_t _capacity;size_t _top;
};int main()
{Stack s;s.Init();s.Push(1);s.Push(2);s.Push(3);s.Push(4);while (!s.Empty()){printf("%d\n", s.Top());s.Pop();}s.Destroy();return 0;
}