文章目录
- 类
- 类定义格式
- 访问限定符
- 类域
- 实例化
- 实例化概念
- 对象大小
- this指针
- 两道nt题目
- 题目一
- 题目二
- C++和C语言实现stack对比
类
类定义格式
- 新增一个关键字
class
,后加上类的名字,{}中为类的主体,类中的函数称为类的⽅法或者成员函数 - 定义在类⾯的成员函数默认为inline
访问限定符
- C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接⼝提供给外部的⽤⼾使⽤
- 有三种访问限定符,public修饰的成员在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后继承章节才能体现出他们的区别
- 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到}即类结束
- ⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public
酱酱,到这里我们就可以看懂下面的代码啦
#include<iostream>
#include<assert.h>
using namespace std;
class Stack
{
public: // 共有就是说,客户可以使用咱们定义的方法
void Init(int capacity = 4)
// 感觉类让函数的实现更加方便,这里不需要写函数的参数,因为就是通过这个类型的变量来调用的这个方法
{
_array = (int*)malloc(sizeof(int) * capacity);
if (nullptr == _array)
{
perror("malloc申请空间失败");
exit(1);
}
_capacity = capacity;
_top = 0;
}
void Push(int x)
{
_array[_top++] = x;
}
int Top()
{
assert(_array);
return _array[_top - 1];
}
void Destroy()
{
free(top > 0);
_array = nullptr;
_top = _capacity = 0;
}
private: // 私有,就是说客户不能使用不能改变的东西
//成员变量
int* _array;
size_t _capacity;
size_t _top;
};
int main()
{
Stack st; // 类名就是对象(变量)类型
st.Init(); // 和结构体一样,通过.操作符来访问类里的元素,这里就是类里的方法
st.Push(1);
st.Push(2);
cout << st.Top() << endl;
st.Destroy();
}
以上就是类的一个基本用法
C++中struct也可以定义类,C++兼容C中struct的⽤法,同时struct升级成了类,明显的变化是struct中可以定义函数,⼀般情况下我们还是推荐⽤class定义类
struct Person
{
public:
void Init(const char* name, int age, int tel)
{
strcpy(_name, name);
_age = age;
_tel = tel;
}
void Print()
{
cout << "姓名:" << _name << endl;
cout << "年龄:" << _age << endl;
cout << "电话:" << _tel << endl;
}
private:
char _name[10];
int _age;
int _tel;
};
int main()
{
Person p;
p.Init("zhangsan", 18, 13409873);
p.Print();
}
class定义成员没有被访问限定符修饰时默认为private,struct默认为public
类域
- 类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时(声明和定义分离),需要使⽤::作⽤域操作符指明成员属于哪个类域
实例化
实例化概念
- ⽤类类型在物理内存中创建对象的过程,称为类实例化出对象
- 类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,⽤类实例化出对象时,才会分配空间
我们看下面代码
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()
{
//Data::_year = 1; //err 这里的对象是类的一部分
Date d1; // 类实例化出对象
Date d2;
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}
对象大小
首先我们思考的问题就是,在计算类的大小的是时候,要不要加上函数地址。结论是不需要加,只需要考虑内存对齐的问题,为什么呢?
我们想,如果两个对象都调用类里的同一个成员函数,这两个成员函数的地址是一样的,我们没有必要创建一个变量,就申请一片空间来存放函数的地址,这样会造成冗余,我们只用把这些方法都放在一个公共的地方,使用的时候直接用就行
而成员变量不一样,每一个对象的成员变量都不相同,所以每一个对象都只用为成员变量开辟空间
下面我们复习一下对齐规则
- 第⼀个成员在与结构体偏移量为0的地址处
- 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
- 注意:对⻬数=编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值
- VS中默认的对⻬数为8
- 结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍
为啥要内存对齐
答:为了提高访问效率
看下面的实例
假设变量在内存中存储不对齐,也就是图中下面的存储方式,我们想要访问_i,就需要访问两次,因为计算机读取数据时是在一个整数倍读取的,比方说三十二位机器,一次就是读取四个字节的数据,且只能在4的整数倍位置读取数据,那么按照图中下面的存储方式,就要访问两次,而按照内存对齐的方式存储,只需要访问一次就可以读取到_i
我们再看一个实例
class A
{
public:
void print()
{
}
private:
char _ch;
int _i;
};
class B
{
void print()
{
}
};
class C
{};
int main()
{
cout << sizeof(A) << endl; // 8
cout << sizeof(B) << endl; // 1
cout << sizeof(C) << endl; // 1
return 0;
}
对于没有成员变量的类,默认开一个字节的空间,起占位作用,不存储有效数据,表示对象存在
this指针
- 我们再返回去看一下Data类,现在就有一个疑问了,d1调⽤Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了⼀个隐含的this指针解决这⾥的问题
- 编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this指针。⽐如Date类的Init的真实原型为,
void Init(Date* const this, int year, int month, int day)
所以调用的时候实际上是这样
d1.Iint(&d1, 2024,3, 31);
只不过指针是被省略掉的,所以实参的取地址也省略
- 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值,this->_year = year;
- C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显⽰使⽤this指针
class Date
{
public: //不可以显示的加
// void Init(Date* const this, int year, int month, int day)
void Init(int year, int month, int day)
{
// 编译报错:error C2106 : “ = ” :左操作数必须为左值
// this = nullptr;
// this->_year = year;
_year = year;
this->_month = month;
this->_day = day;
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
cout << this->_year << "/" << this->_month << "/" << this->_day << endl;//两种都可以
}
private:
// 这⾥只是声明,没有开空间
int _year;
int _month;
int _day;
};
int main()
{
// Date类实例化出对象d1和d2
Date d1;
Date d2;
// d1.Init(&d1, 2024, 3, 31);
d1.Init(2024, 3, 31);
d1.Print();
d2.Init(2024, 7, 5);
d2.Print();
return 0;
}
一个小问题:this指针存在于内存的哪个区域呢?
答案是存在于栈区,因为this是一个隐含的形参,形参就是函数的参数,函数参数存在于栈区
两道nt题目
题目一
class A
{
public:
void Print()
{
cout << "A::Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}
A:编译报错 B:运行崩溃 C:正常运行
这个题答案选C
解析:A选项编译报错,说明是语法错误,首先排除,p虽然是一个空指针,且调用了函数Print,但是成员函数的地址没有存在于*p中,并且Print方法没有访问成员变量,所以不存在解引用,所以正常运行
题目二
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 << this->_a << endl
相当于这样出现了解引用,所以程序崩溃
C++和C语言实现stack对比
C版
#define _CRT_SECURE_NO_WARNINGS 1
#include"stack_c.h"
void STInit(ST* ps)
{
assert(ps);
ps->a = NULL;
ps->top = 0;
ps->capacity = 0;
}
void STDestroy(ST* ps)
{
free(ps->a);
ps->a = NULL;
ps->capacity = ps->top = 0;
}
void STPush(ST* ps, STDataType x)
{
assert(ps);
if (ps->capacity == ps->top)
{
//扩容
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity * 2;
STDataType* tmp = (STDataType*)realloc(ps->a, newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
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 main()
{
ST st;
STInit(&st);
STPush(&st, 1);
STPush(&st, 2);
STPush(&st, 3);
STPush(&st, 4);
STPush(&st, 5);
while (!STEmpty(&st))
{
printf("%d\n", STTop(&st));
STPop(&st);
}
STDestroy(&st);
return 0;
}
C++版
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
typedef int Datatype;
class stack
{
public:
void Init(int n = 4)
{
_a = (Datatype*)malloc(sizeof(Datatype) * n);
if (_a == nullptr)
{
perror("malloc fail");
exit(1);
}
_capacity = 4;
_top = 0;
}
void Push(int x)
{
if (_capacity == _top)
{
int newcapacity = _capacity * 2;
Datatype* tmp = (Datatype*)realloc(_a, newcapacity *sizeof(Datatype));
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:
Datatype* _a;
int _capacity;
int _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;
}
完