C++类和对象 上
·
引言: 为了改善C语言struct的一些不足,C++引入了类。前置知识:C语言结构体
C语言写一个栈的相关内容大致如下
#include<stdio.h>
#include<stdlib.h>
typedef int T;
struct Stack
{
T* arr;
int top; // 栈顶
int capacity; // 总容量
};
void Init(Stack* st){
st->arr = (T*)malloc(sizeof(T) * 100);
st->top = 0, st->capacity = 100;
}
T Top(Stack* st)
{
return st->arr[st->top - 1];
}
void Push(Stack* st, T x){
// 空间满了扩容
if(st->top == st->capacity)
{
st->arr = (T*)realloc(st->arr, sizeof(T) * st->capacity * 2);
st->capacity *= 2;
}
st->arr[st->top++] = x;
}
void Pop(Stack* st){
st->top--;
}
bool Empty(Stack* st){
return st->top == 0;
}
int main()
{
Stack st;
Init(&st);
Push(&st, 1);
// ...
return 0;
}
C++封装封装
C++对结构体进行升级,可以将相关函数放到结构体里面。也就是封装起来
- 结构体里面的变量称为成员变量
- 结构体里面的函数称为成员函数(函数也可以像变量那样通过
.调用)
#include<stdio.h>
#include<stdlib.h>
typedef int T;
struct Stack
{
// Init、Push、Pop、Empty都为成员函数
void Init(Stack* st){
st->arr = (T*)malloc(sizeof(T) * 100);
st->top = 0, st->capacity = 100;
}
T Top(Stack* st)
{
return st->arr[st->top - 1];
}
void Push(Stack* st, T x){
// 空间满了扩容
if(st->top == st->capacity)
{
st->arr = (T*)realloc(st->arr, sizeof(T) * st->capacity * 2);
st->capacity *= 2;
}
st->arr[st->top++] = x;
}
void Pop(Stack* st){
st->top--;
}
bool Empty(Stack* st){
return st->top == 0;
}
// arr、top、capacity都为成员变量
T* arr;
int top; // 栈顶
int capacity; // 总容量
};
int main()
{
Stack st; //st是Stack类的一个对象。用类创建对象的过程,称为类的实例化
st.Init(&st); // 访问成员函数(或成员变量)都用.(指针用->)
st.Push(&st, 1);
return 0;
}
观察C++的成员函数,参数都有Stack* st,函数内部访问成员变量是都是st->变量,函数调用的参数都有&st,显得有些冗余。C++进行了"大统一",用this表示函数里面的st,然后将冗余部分都隐含了起来。简化后的代码如下:
typedef int T;
struct Stack
{
void Init(){
arr = (T*)malloc(sizeof(T) * 100);
top = 0, capacity = 100;
}
/* 实际上代码等同于:
void Init(Stack* this){
this->arr = (T*)malloc(sizeof(T) * 100);
this->top = 0, this->capacity = 100;
}
*/
T Top()
{
return arr[top - 1];
}
void Push(T x){
// 空间满了扩容
if(top == capacity)
{
arr = (T*)realloc(arr, sizeof(T) * capacity * 2);
capacity *= 2;
}
arr[top++] = x;
}
void Pop(){
top--;
}
bool Empty(){
return top == 0;
}
T* arr;
int top;
int capacity;
};
int main()
{
Stack st1, st2;
st1.Init(); // 这里等同于st1.Init(&st1);
st1.Push(1);
return 0;
}
this指针
上文已经初步引入this指针,这里再进行简单介绍。
调用成员函数会自动将对象的地址传给this
class Date
{
public:
void init(int y, int m, int d)
{
_year = y;
_month = m;
_day = d;
}
// // 编译器对上面的函数处理结果如下:
// void init(Date* this, int y, int m, int d)
// {
// this->_year = y;
// this->_month = m;
// this->_day = d;
// }
void Print()
{
printf("%d-%d-%d\n", _year, _month, _day);
printf("%d-%d-%d\n", this->_year, this->_month, this->_day); //this可以在函数体里显式调用,但你不能将this作为形参
// this = nullptr; //err this是个常量
// 严格来说编译器处理后第一个参数应该是Date* const this,this不可修改指向
}
// 注意: 静态成员函数中没有this
static void func() {
// 没有this指针,不能访问非静态成员
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.init(2025, 9, 9); // 编译器处理后的代码:d1.init(&d1, 2025, 9, 9); 会将d1的地址传给this
d1.Print(); // 编译器处理后的代码:d1.Print(&d1);
Date* p = &d1;
p->init(2026, 9, 10);// 指针调用会处理成:p->init(p, 2026, 9, 10);
p->Print(); // 编译器处理后的代码:p->Print(p);
return 0;
}
访问限定符
有时候直接修改结构体成员变量会引起一系列bug(比如获取栈顶元素有人可能直接用st.arr[st.top],但有些人喜欢把栈顶top初始化为0,有些人喜欢初始化为-1…),为此我们希望只通过结构体提供的接口(即成员函数)修改,C++便引入了访问限定符public、private、protected (学继承前可以认为private与protected是一样的),其规则如下:
public修饰的成员可以在类外面直接访问,private修饰的成员不可以在类外面直接访问。(所有成员在类内都可直接访问)- 访问权限的作用域是:该访问限定符到下一个访问限定符之间(没有下一个访问限定符就是该访问限定符到类的结尾)
#include<iostream>
#include<cstdlib>
struct A
{
// 一般习惯将成语函数设为公有,成员变量设为私有
public: // 从此行到private之间的成员的访问权限都是public
int a1;
char c1;
void func1() {
puts("void func1()");
printf("a1:%d c1:%c a2: %d c2: %c\n", a1, c1, a2, c2); // 类内部可访问所有成员
}
private: // 从此行到类结尾的成员的访问权限都是private
int a2;
char c2;
void func2() {
puts("void func2()");
printf("a1:%d c1:%c a2: %d c2: %c\n", a1, c1, a2, c2);
}
};
int main()
{
A t1;
// a1、c1、func1 可以在类外访问
t1.a1 = 1;
t1.c1 = 'a';
printf("%d %c\n", t1.a1, t1.c1);
t1.func1(); // 调用时未初始化a2、c2,打印出来可能是乱码
// // a2、c2、func2 不可以在类外访问
// t1.a2 = 2;
// t1.c2 = 'z';
// t1.func2();
return 0;
}
C++中更喜欢用class代替struct,这两者的区别如下:
class默认访问权限为private,struct默认访问权限为public
class A
{
//...
}
上述代码编译器会在开头加上private,即:
class A
{
private:
//...
}
struct B
{
//...
}
上述代码编译器会在开头加上public,即:
struct B
{
public:
//...
}
成员函数声明和定义分离
class Stack
{
public:
void init(int n);
private:
int* _arr;
int _top;
int _capacity;
};
// 需要指定是哪个类里面的成员函数
void Stack::init(int n)
{
_arr = (int*)malloc(sizeof(int) * n);
_top = 0;
_capacity = n;
}
int main()
{
Stack st;
st.init(4);
return 0;
}
类的大小
类的大小只跟成员变量有关,遵循C语言结构体对齐规则。成员函数是公共的,并不直接存储在对象内部,而是存在于代码段中。
typedef int T;
class Stack
{
public:
void Init(){
arr = (T*)malloc(sizeof(T) * 100);
top = 0, capacity = 100;
}
T Top()
{
return arr[top - 1];
}
void Push(T x){
if(top == capacity)
{
arr = (T*)realloc(arr, sizeof(T) * capacity * 2);
capacity *= 2;
}
arr[top++] = x;
}
void Pop(){
top--;
}
bool Empty(){
return top == 0;
}
private:
T* arr;
int top;
int capacity;
};
int main()
{
cout << sizeof(Stack) << endl; // 你可以删除一个成员函数,可以发现输出结果不变
Stack st1, st2;
cout << sizeof(st1) << endl;
cout << sizeof(st2) << endl;
st1.Init();
st1.Push(1);
st2.Init(); // 通过反汇编可以看出st1与st2调用的是同一个Init()
st2.Push(2);
return 0;
}

类里面若没有成员变量,其大小是1
class A
{};
class B
{
public:
void func()
{}
};
int main()
{
cout << sizeof(A) << endl; // 1
cout << sizeof(B) << endl; // 1
return 0;
}
小练习
下面的代码会编译报错?还是运行报错?还是正常运行?
class A
{
public:
void Print()
{
puts("void Print()");
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
代码能正常运行,Print函数里this指针为空,但没有用this访问成员变量
将上述代码改动一下
class A
{
public:
void Print()
{
cout << _a << endl; // 这里其实是通过隐藏的this访问成员变量
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print(); // 运行崩溃
return 0;
}
更多推荐
所有评论(0)