文章目录
萌新的学习笔记,写错了恳请斧正。
C++中的结构体
我们在C语音中学的结构体在C++依旧适用,而且增加了其他的功能。
在C++中,结构体不仅可以用来定义变量,还可以定义函数。
在这个函数内,还可以直接使用结构体中的变量。
比如之前我们在数据结构中实现的栈现在可以这样全部放在结构体内部:
cpp
#include <iostream>
using namespace std;
typedef int DataType;
struct Stack
{
void Init(size_t capacity)
{
m_arr = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == m_arr)
{
perror("malloc");
return;
}
m_capacity = capacity;
m_size = 0;
}
void Push(const DataType& data)
{
m_arr[m_size] = data;
++m_size;
}
void DataType Top()
{
return m_arr[m_size - 1];
}
void Destory()
{
if (nullptr != m_arr)
{
free(m_arr);
m_arr = nullptr;
m_capacity = 0;
m_size = 0;
}
}
DataType* m_arr;
size_t m_capacity;
size_t m_size;
};
int main()
{
Stack stack;
stack.Init(10);
stack.Push(1);
stack.Push(2);
stack.Push(3);
stack.Push(4);
cout << stack.Top() << endl;
stack.Destory();
return 0;
}
我们发现,这样把一类方法的所有函数和变量封装在一起的方式非常高效,便于代码调用和管理。
在C++中,引入了一个更加适合这样的封装调用的方式,那就是类(Class)。
类的定义
cpp
class classname
{
//类体,包括成员变量和成员函数
}; //注意这里要加分号,与结构体类似
class
是C++的关键字,用于定义类;classname是类的名字;花括号中是类的主体。
同样的,我们可以在类体中放成员变量和成员函数,但是类中成员函数的定义可以有两种方式:
-
定义与声明都放在类中,对于一些简单的小函数直接定义在类中比较方便:
cppclass Date { void SetDate(int y, int m, int d) { m_year = y; m_month = m; m_day = d; } int m_year; int m_month; int m_day; }
在这种情况下,编译器更倾向于将这样的函数作为内联函数处理。
-
定义放在类外,声明放在类中,这样可以让拥有较多较大函数的类看起来更直观:
cpp#include <iostream> using namespace std; typedef int DataType; class Stack { void Init(size_t capacity); void Push(const DataType& data); DataType Top(); void Destory(); DataType* m_arr; size_t m_capacity; size_t m_size; }; void Stack::Init(size_t capacity) { m_arr = (DataType*)malloc(sizeof(DataType) * capacity); if (nullptr == m_arr) { perror("malloc"); return; } m_capacity = capacity; m_size = 0; } void Stack::Push(const DataType& data) { m_arr[m_size] = data; ++m_size; } DataType Stack::Top() { return m_arr[m_size - 1]; } void Stack::Destory() { if (nullptr != m_arr) { free(m_arr); m_arr = nullptr; m_capacity = 0; m_size = 0; } }
我们发现,这样声明与定义分离可以让类的结构变的简单明了。但是在类外再对这些函数作定义时就需要在函数名前加上类名和作用域限定符,就像上一篇中的命名空间域一样。
其实,这也是一个域,叫类域。
类域
类域与局部域、全局域、命名空间域类似,一个类中的成员变量和成员函数都属于这个类域。
访问限定符
在C++中,类与结构体的成员是有访问权限这么一个属性的。访问权限有public(公有) 、private(私有)、**protected(保护)**三种。我们可以通过访问限定符来设定成员的访问权限:
cpp
#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:
void Init(size_t capacity);
void Push(const DataType& data);
DataType Top();
void Destory();
private:
DataType* m_arr;
size_t m_capacity;
size_t m_size;
};
void Stack::Init(size_t capacity)
{
m_arr = (DataType*)malloc(sizeof(DataType) * capacity);
if (nullptr == m_arr)
{
perror("malloc");
return;
}
m_capacity = capacity;
m_size = 0;
}
void Stack::Push(const DataType& data)
{
m_arr[m_size] = data;
++m_size;
}
DataType Stack::Top()
{
return m_arr[m_size - 1];
}
void Stack::Destory()
{
if (nullptr != m_arr)
{
free(m_arr);
m_arr = nullptr;
m_capacity = 0;
m_size = 0;
}
}
我们看到,在上面的程序中,从public:
之后,一直到下一个访问限定符之前的内容都会被设置为公有访问权限,而private和protected同理。
那么不同的访问权限到底意味着什么呢?
- public修饰的成员在类外可以直接被访问。
- protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)。
- 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止。
- 如果后面没有访问限定符,作用域就到子即类结束。
- 如果没有加访问限定符,class的默认访问权限为private,struct为public。(因为struct要兼容C)
类的实例化
用类类型创建对象的过程,称为类的实例化:
-
类是对对象进行描述的 ,是一个模型 或者图纸 一样的东西,限定了类有哪些成员。与结构体相同,定义出一个类并没有分配实际的内存空间来存储它。
-
一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类的成员变量(不包括成员函数,原因下面说)。
cppclass Date { public: void SetDate(int y, int m, int d) { m_year = y; m_month = m; m_day = d; } private: int m_year; int m_month; int m_day; } int main() { Date date; //实例化,对象date就是类Date实例化的结果 date.SetDate(2024, 5, 11); return 0; }
类对象模型
我们如何计算类对象的大小?这就需要我们知道类对象是如何存储的。
其实很简单,我们用sizeof
试试就知道,类对象的大小只包括类的成员变量的大小(与结构体一样遵循内存对齐规则,详见C语言笔记#28)
那么成员函数呢?
成员函数放在公共代码区的一个类成员函数表中。
this指针
我们还是以日期类为例:
cpp
class Date
{
public:
void SetDate(int y, int m, int d)
{
m_year = y;
m_month = m;
m_day = d;
}
private:
int m_year;
int m_month;
int m_day;
}
int main()
{
Date date1;
date1.SetDate(2024, 5, 11);
Date date2;
date2.SetDate(2024, 5, 12);
return 0;
}
我们看到,对于Date类的两个对象,我们都调用了函数SetDate。
但是这个函数只有三个参数y、m和d,而且上面提到类成员函数是储存在公共代码区的,所以两次调用的是同一个地址。
那么SetDate函数是如何知到调用时应该对那一个对象进行操作的呢?为什么date1.SetDate(2024, 5, 11);
不会去修改对象date2的成员变量呢?
在C++中,是通过引入this
指针来解决这个问题的:
C++编译器给每个"非静态的成员函数"增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有"成员变量"的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。
所以上面函数定义中也可以这样写:
cpp
void SetDate(int y, int m, int d)
{
this->m_year = y;
this->m_month = m;
this->m_day = d;
}
this指针的特性
- this指针的类型:
类的类型* const
,const意味着在成员函数中不能给this指针赋值。 - 只能在成员函数内部使用。
- 本质上是成员函数的形参,当对象调用成员函数时将对象的地址作为实参传递给this实参。所以对象中不存储this指针。
- this指针是成员函数的第一个隐含的指针形参,一般由ecx寄存器自动传递,不需要用户传递。
对比C与C++Stack的实现
C语言
c
#pragma once
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
//数据类型
typedef int STDataType;
typedef struct Stack
{
STDataType* _data;
int _top;
int _capacity;
} Stack, * pStack;
//栈的初始化
void InitStack(pStack pst);
//压栈
void StackPush(pStack pst, STDataType x);
//弹栈
void StackPop(pStack pst);
//栈顶元素
STDataType StackTop(pStack pst);
//栈大小
int StackSize(pStack pst);
//栈判空
_Bool StackEmpty(pStack pst);
//栈销毁
void StackDestory(pStack pst);
c
#include "Stack.h"
//栈的初始化
void InitStack(pStack pst)
{
assert(pst);
pst->_data = NULL;
pst->_top = 0;
pst->_capacity = 0;
}
//容量检查扩容
void StackCheck(pStack pst)
{
if (pst->_top == pst->_capacity)
{
int newCapacity = ((pst->_capacity == 0) ? 4 : (2 * pst->_capacity));
STDataType* temp = (STDataType*)realloc(pst->_data, newCapacity * sizeof(STDataType));
if (temp == NULL)
{
perror("realloc");
exit(EXIT_FAILURE);
}
pst->_data = temp;
pst->_capacity = newCapacity;
}
}
//压栈
void StackPush(pStack pst, STDataType x)
{
assert(pst);
StackCheck(pst);
memcpy(&(pst->_data[pst->_top]), &x, sizeof(STDataType));
pst->_top++;
}
//弹栈
void StackPop(pStack pst)
{
assert(pst);
assert(!StackEmpty(pst));
pst->_top--;
}
//栈顶元素
STDataType StackTop(pStack pst)
{
assert(pst);
return pst->_data[pst->_top - 1];
}
//栈大小
int StackSize(pStack pst)
{
assert(pst);
return pst->_top;
}
//栈判空
_Bool StackEmpty(pStack pst)
{
assert(pst);
return pst->_top == 0;
}
//栈销毁
void StackDestory(pStack pst)
{
assert(pst);
free(pst->_data);
pst->_capacity = 0;
pst->_top = 0;
pst->_data = NULL;
}
C++
先忽略Stack函数与~Stack函数,下一篇笔记讲。
cpp
#pragma once
#include <iostream>
#include <string>
using namespace std;
typedef int StackDataType;
class Stack
{
public:
Stack(int size);
~Stack();
void Push(StackDataType data);
StackDataType Pop();
StackDataType Top();
bool IsEmpty();
bool IsFull();
int GetSize();
int GetTop();
private:
StackDataType* m_data;
int m_size;
int m_capacity;
};
cpp
#include "Stack.h"
Stack::Stack(int size = 4)
{
m_data = new StackDataType[size];
m_size = 0;
m_capacity = size;
}
Stack::~Stack()
{
delete[] m_data;
}
void Stack::Push(StackDataType data)
{
if (IsFull())
{
cout << "Stack is full!" << endl;
return;
}
m_data[m_size++] = data;
}
StackDataType Stack::Pop()
{
if (IsEmpty())
{
cout << "Stack is empty!" << endl;
return -1;
}
return m_data[--m_size];
}
StackDataType Stack::Top()
{
if (IsEmpty())
{
cout << "Stack is empty!" << endl;
return -1;
}
return m_data[m_size - 1];
}
bool Stack::IsEmpty()
{
return m_size == 0;
}
bool Stack::IsFull()
{
return m_size == m_capacity;
}
int Stack::GetSize()
{
return m_size;
}
int Stack::GetTop()
{
return m_size - 1;
}