一、类与结构体前世孽缘
在C语言中,我们要描述一本书的是,需要去指出书名,作者,出版日期,价格;而仅仅从数据的单个属性去介绍这个书,是完全不够的。所以我们引入了结构体;结构体中允许,包含多个数据类型,如字符、整型、浮点型、数组、指针等等。而这些数据类型被称为成员变量,又称作成员属性。——【C语言】结构体与位段-CSDN博客
- 好消息,在C++中,结构体进一步升级成为了类
- C++兼容C语言中结构体的用法
- 类不但可以包含多个成员变量,而且可以包含成员函数(函数的声明与定义)。
- 成员变量又叫做类的属性,成员函数又叫做类的方法
- 在类中定义与声明合并的函数方法,默认是内联函数。
- 创建对象的时候,只需要类名即可,不用再加上类的关键字
- 声明类的关键字又引入一个:class(类、类型)
- class与struct声明类的使用,除了默认访问限定符不一样,其余的一致。
二、类的基础概念
1.类的定义格式
- 类的定义格式:class myclassName { //类的方法、类的属性 };
2.对象的内存占用
用类去创建一个对象,系统会在内存中为其开辟空间;那么问题又来了:对象所占用的内存大小该如何计算呢?——对象中主要存储的是成员变量的数据,而成员变量的内存存储遵循内存对齐的原则。
- 问:对象是如何去访问类的方法的?
- 答:函数在经过编译后会生成一段代码指令,代码指令是无法存储在对象中的,而是存储在代码段中的,而函数的地址就是第一条指令的地址,对象通过该地址去调用函数。
- 问:类的属性存储在对象中毋庸置疑,但问题是:函数地址是存储在对象中的吗?
- 答:错,函数地址也并非存在对象中,也是在代码段中;因为,不同的对象调用函数的时候,都是通过相同的函数地址去调用的,所以将函数地址存储在内存中,会造成内存的浪费。
- 问:若类中没有成员变量,只有成员函数时,对象在占用内存的大小会不会是0呢?
- 答:不会,没有成员变量的对象,占用内存的大小为1;若对象的大小是0,那怎么在内存中标识对象是否存在呢?你说呢?
3.类的访问权限
讲起访问限定符了,就不得不提一下C++的其中一个特性:封装,在C++中,不太喜欢(也不愿)能在外部,随意访问与修改数据的大小,又或者个人不想让别人使用某个类的方法或访问某个成员数据,与是就引入的访问限定符:public、project、private。
- 通过访问限定符对成员变量和成员函数进行修饰,可以对其访问权限进行修改
- public修饰的成员变量与成员函数,可通过对象进行访问;
- private与project修饰的成员变量与函数,不可通过对象进行访问;
- 访问限定符的作用域在两个访问限定符之间,又或者是类结束——‘}’
- class声明的类,默认是private修饰
- struct声明的类,默认是public修饰
4.类域
- 类形成了一个新域,在类外,定义成员变量与函数的时候,需要通过域操作符(::)去访问。
- 编译器默认查找路径是,当前域,全局域,若找不到,则判定链接错误。
- 定义成员变量,这里定义的成员变量是指的是,定义一个静态的成员变量,而不是非静态的。
- 定义成员函数,是用于函数声明与定义分离的时候,在类外,定义函数的使用,需要告诉编译器我定义的函数是在某个域中的。
class ZMH
{
public:
static void Print();
private:
static int _test;
int _a;
int _b;
};
// 成员变量的定义
int ZMH::_test = 1314;
// 成员函数的定义
void ZMH::Print()
{
cout << ZMH::_test;
}
int main()
{
ZMH::Print();
return 0;
}
三、抽象与实例化
在现实生活中,建造别墅需要用到设计图纸:里面包含了别墅的大部分信息,以及别墅的整体轮廓。而建筑工人则根据这种设计图纸,可以建造出一套别墅,且可以建造多套别墅。也可以存在多套别墅,允许这多套别墅相同,或有些细节不同。
而类就好似一张设计图片,创建对象类似于建造一套别墅。类是对象的抽象,对象是类的实例化。所以,声明类的时候,是没有开辟成员变量的空间的,而用类创建对象的时候,才是真正的开辟空间。
四、相关代码+注释解析
下面是用类封装了一个顺序表,主要是想让大家,可以结合着代码,和代码注释,对类的基本概念,有更深入的理解。
#include<iostream>
#include<assert.h>
using namespace std;
typedef int T; // 本来是个类的模板,因为还未涉及;
class SeqList
{
public:
SeqList(int n = 4)
:_val(new T[n])
, _size(0)
, _capacity(n) {}
~SeqList()
{
delete[] _val;
_size = _capacity = 0;
}
void Chack() //函数直接在类中定义与声明一起,默认内联函数。
{
if (_size >= _capacity)
{
T* tmp = new T[2 * _capacity];
memcpy(tmp, _val, sizeof(T) * _capacity);
delete[] _val;
_val = tmp;
_capacity *= 2;
}
}
void Print() //函数直接在类中定义与声明一起,默认内联函数。
{
for (int i = 0; i < _size; ++i)
cout << _val[i] << " ";
cout << endl;
}
void Insert(T x, int k); // 函数声明
void Remove(int k);
int Find(T x);
void Revise(T x, int k);
private: //私有,类外不能直接访问
T* _val;
int _size;
int _capacity;
};
void SeqList::Insert(T x, int k) //声明与定义分类,如何去定义类域中的函数
{
Chack();
assert(k >= 1 && k <= _size + 1);
int end = _size - 1;
while (end >= k - 1)
{
_val[end + 1] = _val[end];
end--;
}
_val[k - 1] = x;
++_size;
}
void SeqList::Remove(int k)
{
assert(k >= 1 && k <= _size);
int start = k - 1;
while (start + 1 < _size)
{
_val[start] = _val[start + 1];
start++;
}
--_size;
}
int SeqList::Find(T x)
{
for (int i = 0; i < _size; ++i)
{
if (_val[i] == x)
{
return i;
}
}
return -1;
}
void SeqList::Revise(T x, int k)
{
assert(k >= 1 && k <= _size);
_val[k - 1] = x;
}
int main()
{
SeqList s1(4); // 通过声明的类创建对象
s1.Insert(1, 1); // 通过对象,函数方法的调用
s1.Insert(2, 1);
s1.Insert(3, 1);
s1.Insert(4, 1);
s1.Insert(5, 1);
s1.Print();
cout << s1.Find(1) << endl;
s1.Remove(3);
s1.Print();
s1.Revise(1314, 3);
s1.Print();
return 0;
}
五、this指针
- 类的方法是存储在代码段中的,不同的类,调用相同的代码指令,那么问题就来了:
- 不同的对象是调用函数的时候,是如何访问不同对象中的数据呢?编译器又是如何知道,我要访问哪个对象的数据呢?
- 这里,就不得不提一下隐式指针的功能了——它是由this关键字起作用,其指向的是,通过谁调用函数的,谁。
- this指针不可在形参和实参中显示存在,编译器会自己做处理。
- 但是在函数内部,this指针可以显示存在。
int main()
{
SeqList s1(4);
//s1.Insert(&s1,1,1); ->编译器在调用的时候,在第一个形参前面的位置,加上对象的地址,
s1.Insert(1, 1);
s1.Insert(1, 1);
s1.Insert(1, 1);
s1.Insert(1, 1);
SeqList s2;
s2.Insert(2, 1);
s2.Insert(2, 1);
s2.Insert(2, 1);
s2.Insert(2, 1);
//s1.Insert(&s1,1,1); ->编译器会自己做处理,
s1.Print(); // 结果是1 1 1 1
//s1.Insert(&s2,1,1);->在形参和实参位置,不可显示调用,
s2.Print(); // 结果是2 2 2 2
return 0;
}