目录
1. 类的引入:
2. 类的定义:
2.1类的定义以及基本结构:
2.2 类的访问限定符:
3. 类的声明与定义的分离:
4. 类的实例化:
5. 类的大小计算:
1. 类的引入:
在数据结构系列的文章中,经常会使用结构体来构建例如顺序表,链表等数据结构的大体结构。在C语言中,结构体内部只能定义变量。但是在C++中,对结构体进行了修改,称之为类,类中不但可以定义变量,也可以定义函数。同时,类也兼容了C语言中结构体的所有用法。为了具体体现两种语言中结构体的不同,下面将通过一个示例进行解释:
在数据结构的栈这一部分中,对于栈这一数据结构的定义可以由下面的代码表示:
struct Stack
{
int* a;
int top;
int capacity;
};
void StackInit(struct Stack* ps);
void StackPush(struct Stack* ps, int x);
(注:此处只简单的引入栈的基本结构以及部分功能函数的声明,用于解释类和结构体的不同)
在C语言中,如果不对结构体进行来重命名,则在使用时,必须带上。在C++中,对这部分进行了优化,在使用结构体时,只需要结构体名即可。例如对于上述代码,在C语言中,如果想要使用结构体,需输入
struct Stack S1;
而在C++中使用时,类名即类型,不需要加上。即:
Stack S1;
上面提到了,在C++中,类(结构体)中可以定义函数,如果将开头关于栈的相关代码利用C++进行修改,可以很好的简化代码书写,即:
struct Stack
{
int* a;
int top;
int capacity;
void Init();
void Push(int x);
};
从上面给出的代码不难看出,利用C++对栈的相关代码进行改写时,不但让函数中的参数数量减少,并且还化简了C语言中与栈相关的功能函数的函数名。例如向栈中插入元素,C语言中此函数的函数名为,在C++中为。这是因为在C语言中,由于结构体内部不能定义函数,并且对于其他种类的数据结构中,均有初始化函数。如果不在函数名之前加上不同种类数据结构的名字加以区分,并且在C语言中并不存在函数重载。会造成错误。
而在C++中,因为类的作用域(对于类的作用域将在文章后部给予相关解释,本处只是提出这个概念关系,并且可以在类中定义函数,因此,可以在不同的类中,同时定义
2. 类的定义:
2.1类的定义以及基本结构:
上面给出了定义类的一个方法,即利用,本部分给出另一个用于定义类的关键字,具体结构如下:
class className
{
// 类体:由成员函数和成员变量组成
};
其中,为定义类的关键字,为类的名字,{}中为类的主体,类主体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
2.2 类的访问限定符:
对于类的访问限定符,为,,
三者之间的区别为,修饰的成员变量可以在类外访问。而修饰的成员变量只能在类中被访问。
对于三个访问限定符的作用权限,可分为下面两种情况:
1.如果任意访问限定符(例如下面给出代码中访问限定符)后面的代码中存在其他的访问限定符(例如下面代码中的),则的作用权限在遇到后截止。例如:
class Stack
{
private:
int* a;
int top;
int capacity;
public:
void Init();
void Push(int x);
};
2.如果任意的访问限定符(例如下面代码中的)中截止到类的"}"之前没有其他的访问限定符,则的访问权限作用截止到类的末尾。例如:
class Stack
{
private:
int* a;
int top;
int capacity;
public:
void Init();
void Push(int x);
};
对于这两个关键字都可以来定义类。二者的不同主要体现在默认的访问权限上,对于,其默认的访问权限为。对于,其默认的访问权限为。
3. 类的声明与定义的分离:
在之前数据结构的章节中,经常会让函数的声明和定义分离在两个文件中,对于类而言,加入在类中给出一个函数的声明,在外部给出函数的定义,方法如下:
void Stack::Init()
{
a = 0;
top = 0;
capacity = 0;
}
除了上述情况,如果将函数的声明与定义同时放在类中,会出现两种情况:
1. 函数的代码行数较少,此时函数会自动变为内联函数。
2. 函数的代码行数较多,函数不会变成内联函数。
class Stack
{
private:
int* a;
int top;
int capacity;
public:
void Init();
void Push(int x)
{
a[top] = x;
capacity++;
top++;
}
};
因此,对于较为短小的函数,适合之间定义在类中,而较为复杂的函数适合采用声明与定义分离的方法。
4. 类的实例化:
定义如下:用类类型创建对象的过程,称为类的实例化。类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类,并没有分配实际的内存空间来存储它。例如:
class Stack
{
public:
int _year;
int _month;
int _day;
};
在类中给出上面三个变量,需要注意,类中的变量,只是变量的声明,这种变量并没有在内存中开辟空间,在没有进行类的实例化的情况下,并不能进行使用,例如:
int main()
{
Stack::_day = 2023;
return 0;
}
如果运行上述给出的代码,编译器会显示报错。这是因为此时的变量只是一个声明,并没有在内存中开辟空间。
如果想要使用上述变量,首先需要进行一次类的实例化,代码如下:
int main()
{
Stack Time; //实例化
Time._day = 12;
Time._month = 11;
Time._year = 2023;
return 0;
}
5. 类的大小计算:
例如对于下面给出的类:
class Stack1
{
void Push(int x)
{
}
public:
int _year;
int _month;
int _day;
};
class Stack2
{
public:
int _year;
int _month;
int _day;
};
不难发现,上述代码给出的两个类中,不同点为中包含了一个函数,中只声明了三个整型变量。通过下面的代码打印其大小:
int main()
{
Stack1 time;
Stack2 time1;
cout << sizeof(time) << endl;
cout << sizeof(time1) << endl;
return 0;
}
结果如下:
不难发现,二者大小的值是相同的,也就是说,计算类的大小时,并没有将类中的函数计算在内。这是因为,在每次调用时,类中的变量都会接收不同的值,而函数除了传递的形式参数没有任何变化,因此,在类中,变量是独立存储的,而函数,则是在一个公共空间中存储的。
如果将上述的类进行部分更改,即:
class Stack3
{
public:
char _year;
int _month;
int _day;
};
此时打印结果为:
通过打印结果不难发现,类中变量的存储依旧满足内存对齐原则,即:
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
在类的大小这部分,还有一个特殊的例子,即空类:
class Stack4
{
};
当打印空类的大小时,即:
int main()
{
Stack4 a;
cout << sizeof(a) << endl;
return 0;
}
结果如下:
因此,对于上述无成员变量的类的对象(a),大小为1字节。这1字节并不存储有效数字,仅用来表示定义的对象(a)存在。