个人专栏:《数据结构-初阶》《经典OJ题目》《C语言》《小白算法成长录》
欢迎大佬交流
本文代码已同步GitHub:GitHub - Stellen-z/DailyCode: pracetice · GitHub
一、类的定义
1、类定义格式
-
类名 :
PascalCase(如StudentManager) -
访问标签 :
public:、protected:、private:后加冒号,独占一行,缩进与类同级 -
成员变量 :建议使用
m_前缀或尾随_(如m_age或age_) -
方法定义 :小驼峰或下划线分隔(如
getValue()或get_value()) -
分号 :类定义结束后必须加分号
};
C++中struct也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,那么struct中也可以定义函数
定义在类里面的成员函数默认为inline
下面给出一个例子
cpp
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
//区分成员变量,加上_
int _year;
int _month;
int _day;
};
接着来看struct的升级
cpp
#include <iostream>
using namespace std;
//struct升级为类
//1.struct里面可以定义函数
//2.名称即类型
//兼容C语言
typedef struct ListNodeC
{
int val;
struct ListNodeC* next;
}ListNodeC;
//C++名称即代表类型
struct ListNodeCPP
{
void Init(int x)
{
next = nullptr;
val = x;
}
ListNodeCPP* next;
int val;
};
2、访问限定符
访问限定符是C++一种实现封装的方式,将类和对象的属性与方法结合,通过访问选择性地将其接口提供
-
public:接口部分,对外暴露(如成员函数、构造函数)。
-
private :隐藏实现细节(推荐成员变量默认为
private)。 -
protected:为派生类提供"继承但封装"的能力,常用于基类的扩展点。
-
默认限定符 :
class默认private,struct默认public。 -
顺序灵活 :通常先写
public接口,再写protected/private实现。
3、类域
类域指的是类定义内部所形成的作用域
a、核心特点
-
类的成员(数据成员、成员函数、嵌套类型)位于该类独有的作用域内。
-
在类外访问成员时,必须通过
对象名.成员、指针->成员或类名::静态成员的方式。 -
在类内(成员函数体中)直接使用成员名即可,编译器会自动在当前类域中查找。
b、作用域解析符 ::
-
用于在类外定义成员函数:
ReturnType ClassName::methodName() { ... } -
用于区分同名的全局变量和类静态成员:
ClassName::member
c、嵌套作用域
-
类域可以嵌套(如成员函数内的局部作用域、嵌套类)。
-
内部作用域可以访问外部类域中的成员(只要权限允许)。
类域影响的是编译的查找规则,我们通过下面例子来理解
cpp
#include <iostream>
using namespace std;
class Stack
{
public:
//成员函数
void Init(int n = 4);
private:
//成员变量
int* arr;
int _top;
int _capacity;
};
//没有指定类域
void Init(int n)
{
////error C2065: "arr": 未声明的标识符
arr = (int*)malloc(sizeof(int) * n);
if (nullptr == arr)
{
perror("malloc failed!\n");
exit(1);
}
// error C2065:"_top": 未声明的标识符
_top = 0;
//error C2065:"_capacity": 未声明的标识符
_capacity = 0;
}
void Stack::Init(int n)
{
////error C2065: "arr": 未声明的标识符
arr = (int*)malloc(sizeof(int) * n);
if (nullptr == arr)
{
perror("malloc failed!\n");
exit(1);
}
// error C2065:"top": 未声明的标识符
_top = 0;
//error C2065:"capacity": 未声明的标识符
_capacity = 0;
}
int main()
{
Stack st;
st.Init();
return 0;
}
解释:函数定义时没有指定类域,在编译时就会在全局中找声明,显然找不到,就会报错
而指定类域Stack,即当全局域找不到时就回去类域中查找
二、实例化
1、实例化的概念
实例化(Instantiation)是指根据类(类型蓝图)创建具体对象(实例)的过程。
类是对象的一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量;
这些成员变量只是声明,并没与分配空间;
用类进行实例化时,才会分配空间
对象 = 类的实例,拥有独立的成员变量副本
同样,我们通过例子来讲解
cpp
#include <iostream>
using namespace std;
class Date
{
public:
void Init(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
//成员变量在类中只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
//实例化对象d1,d2
Date d1;
Date d2;
d1.Init(2026, 6, 7);
d1.Print();
d2.Init(2026, 7, 7);
d2.Print();
return 0;
}
2、对象大小
实例化出来的每个对象都有独立的空间;
对象里面包含成员变量,成员函数;
a、成员变量
对象的数据空间中肯定包含成员变量;
那么成员变量按照什方式存储呢?
答案是按照内存对齐规则
内存对齐规则:
基本对齐原则
-
每个成员 的起始偏移量必须是其自身大小的整数倍(或编译器默认对齐值的较小者)。
-
整个结构体 的大小必须是其最大成员对齐要求的整数倍(末尾可能填充)。
默认对齐值
-
基本类型对齐 = 其大小(
char=1,short=2,int=4,double=8 等)。 -
指针:8 字节(64位系统)。
-
编译器可设定默认最大对齐值(如 MSVC 默认 8,GCC 默认 8/16 等)。
b、成员函数
函数名就是函数对应的地址,如果要存储函数,就需要存储函数指针;
分析一下:实例化的对象,每次调用的成员函数是否相同?
调用的成员函数是相同的!
为什么?
我们通过汇编指令来看
显然,两个对象调用函数的地址完全相同
三、this指针
1、this指针的用处
我们先来看看实例化的例子中代码运行情况
调用Init 和 print 函数时,函数时怎样知道访问的是d1还是d2呢?
关键就是隐含的this指针
在编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类型的 this 指针;
对于Init来说,原型为:void Init(Date* const this,int year, int month, int day)
在成员函数中访问成员变量也是通过 this指针进行访问
对于Init来说,即为
cpp
void Init(Date* const this, int year, int month, int day)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
2、经典题目
下面来看两道经典的题目
a、Eg1
1、程序运行结果为? A、编译报错 B、运行崩溃 C、正常运行
cpp
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
分析:
首先排除A选项,代码中并没有语法错误;
接着考虑 p-> 这是对 p 的解引用吗?如果是的话那就选择B选择
来看运行结果
显然程序正常运行,那就说明没有对 p 进行解引用
前面我们说过,调用的函数地址都是一样的,不同的是传给 this 指针的地址不同; 
那么在汇编指令上, 首先将 p 传给 this 指针,接着call 函数地址进行调用
进入成员函数之后,直接进行打印,程序正常运行
b、Eg2
1、程序运行结果为? A、编译报错 B、运行崩溃 C、正常运行
cpp
#include <iostream>
using namespace std;
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
cout << _a << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
分析:这道题和上道题唯一的区别就是在成员函数内加了一句cout << _a << endl; 这会导致什么?
根据第一题分析的经验,我们知道传参是将p 传给了 this指针,那么this也就是空指针;
而访问成员变量就是通过this指针进行访问的;
那么就会造成 空指针解引用! 导致程序崩溃!
四、初探封装
面向对象三大特性:封装、继承、多态
首先来对比下面C语言实现的Stack和当前知识下C++实现的Stack
C语言版
cpp
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}Stack;
// 初始化栈
void StackInit(Stack* ps)
{
assert(ps);
ps->_a = NULL;
ps->_capacity = ps->_top = 0;
}
// 入栈
void StackPush(Stack* ps, STDataType data)
{
assert(ps);
if (ps->_capacity == ps->_top)
{
int newcapacity = ps->_capacity == 0 ? 4 : 2 * ps->_capacity;
STDataType* tmp = (STDataType*)realloc(ps->_a, sizeof(STDataType) * newcapacity);
if (tmp == NULL)
{
perror("realloc failed!\n");
exit(1);
}
ps->_a = tmp;
ps->_capacity = newcapacity;
}
ps->_a[ps->_top++] = data;
}
// 出栈
void StackPop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0);
ps->_top--;
}
// 获取栈顶元素
STDataType StackTop(Stack* ps)
{
assert(ps);
assert(ps->_top > 0);
return ps->_a[ps->_top - 1];
}
// 获取栈中有效元素个数
int StackSize(Stack* ps)
{
assert(ps);
return ps->_top;
}
// 检测栈是否为空,如果为空返回非零结果,如果不为空返回0
bool StackEmpty(Stack* ps)
{
assert(ps);
return ps->_top == 0;
}
// 销毁栈
void StackDestroy(Stack* ps)
{
free(ps->_a);
ps->_a = NULL;
ps->_capacity = ps->_top = 0;
}
int main()
{
Stack st;
STInit(&st);
StackPush(&st,1);
StackPush(&st,2);
StackPush(&st,3);
StackPush(&st,4);
while(!StackEmpty(&st))
{
printf("%d ",StackTop(&st));
StackPop(&St);
}
return 0;
}
C++版
cpp
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
class Stack
{
public:
void Init(int n = 4)
{
_a = (STDataType*)malloc(sizeof(STDataType) * n);
if (_a == NULL)
{
perror("malloc failed!\n");
exit(1);
}
_top = 0;
_capacity = 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;
int _top;
int _capacity;
};
int main()
{
Stack st;
st.Init();
st.Push(1);
st.Push(2);
st.Push(3);
st.Push(4);
while (!st.Empty())
{
cout << st.Top() << " ";
st.Pop();
}
st.Destroy();
return 0;
}
对比之后,我们发现,底层逻辑依然没变
但实现形态上发生了变化
-
C++中变量和函数都放在了类里面,通过访问限定符进行限制,不能随意进行修改
-
C++中不再需要
typedef来简写,直接用类型即可
如果觉得有帮助,可以关注 GitHub 项目持续更新:GitHub - Stellen-z/DailyCode: pracetice · GitHub