在C语言中,我们都知道,变量(数据)和函数(处理数据的操作)很少存在直接的关联,我们一般也不会将它当做一个整体去看待,并且C语言本身也并不支持我们这样去做。所以的数据由一组“分布在各个以功能为导向的函数中”的算法所驱动,它们处理的是共同的外部数据。就算是结构体,也仅仅只能抽象描述一个事物所具有的属性,仅此而已。
但在C++中,结构体不仅仅可以定义变量,也可以定义函数。
这样做有什么好处呢?在我们编程语言的发展中,我们发现,现实世界的事物更多的情况除了具有其本身的属性外,还具有一些功能。
举个栗子:一头羊,有四条腿,一个头,两只眼睛,这就是羊本身所具有的属性。但羊本身除了具有这些属性之外,还有吃草这样的功能,睡觉这样的功能。那么我们就可以通过结构体来将这一只羊完整的描述起来,不仅仅是羊的属性,还有羊的一些行为动作。
而在C++中,结构体struct更多的时候是被class关键字来代替的,class被称之为类。
class className{
//类体:由函数和变量组成
}; //注意!!!这里有分号
class为定义类的关键字,className为类名,{}中为类的主体,注意类定义结束时后面的分号不能忘记。类中的元素称为类的成员;类中的数据称类的属性或类的成员变量;类中的函数称为类的方法或者类的成员函数。
类的定义通常有两种方式:
- 类的声明和定义全部放在类体中
- 类的声明放在.h文件中,类的定义放在.cpp文件中,但在定义的时候要加::作用域限定符
#include<iostream>
using namespace std;
class Std{
public:
void Print()
{
cout << _name << " " << _male << " " << _age << endl;
}
public:
char* _name;
char* _male;
int _age;
};
int main()
{
Std s;
s._name = "Bob";
s._male = "boy";
s._age = 22;
s.Print();
return 0;
}
那么既然C语言中有struct,C++中也有struct,它们之间的区别在哪呢?
C语言中的struct只能定义成员变量,不能定义成员方法,并且在带名字的声明过后,定义结构体的时候仍然需要加struct,C++中除了定义成员变量还可以定义成员方法,声明过后,定义对象时只需要用名称来定义。
那么C++中既有结构体struct又有类class,它们之间的区别呢?
事实上这两个关键字基本是可以划等号的,只有一些微小的区别,struct中,不加访问限定符,默认是公有的,class中,不加访问限定符,默认是私有的。
那么什么是访问限定符呢?
访问限定符有三个:
public(公有)
protected(保护)
private(私有)
但访问限定符具体有什么作用呢?
这得稍微聊一聊C++三大特性:封装、继承、多态中的封装。
封装:隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和操作数据的方法进行有机结合。
- public成员在类外可以直接访问
- protected和private成员在类外不能访问
- 它们的作用域从访问限定符出现的位置开始直到下一个访问限定符出现时为止
注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
那么如何在类外访问一个类中的私有成员变量呢?
我们可以定义一个公有的成员函数,通过公有的成员函数所提供的接口来访问私有的成员变量,这样做是为了更加安全。
一个类定义成员变量,定义了成员方法,除此之外,它还定义了新的作用域。类的所有成员都必须处在类的作用域中。形参表和和函数体处于类的作用域中。在类体外定义成员,需要::作用域限定符指明成员属于哪个类域。在类的作用域外,只能通过对象借助成员访问操作符.
和->
来访问成员,跟在访问操作符后面的名字必须在相关联的作用域中。
并且成员变量在类中具有全局作用域,定义在成员函数之前或之后都可以。
我们都知道,结构体在声明之后,就相当于一个类型。而类也如此,并且用类类型创建对象的过程,称为类的实例化。
和其他的内置类型一样,类也仅仅只是一个模板一样的东西,用这个类制造出来的对象,就要符合这个类模板的样子。所以,定义一个类并没有分配实际的内存空间类存储它。
只有进行实例化之后,对象才会占据实际的空间。一个类可以实例化多个对象,就像一个int可以定义多个不同的变量一样的道理。
那么类实例化之后,它占多大空间呢?
一个类中既有成员变量,也有成员方法,我们如何来计算它所占用的空间大小呢?
方案1:每一个对象的成员变量和成员方法之和就是它所占空间的大小。
当然不是了!每个对象的成员变量的值可能不同,但是函数是相同的,如果一个类创建多个对象,每个对象中都会保存一份代码,相同的代码保存多次,这也是浪费空间的行为。
方案2:成员变量单独存储,并且多给一个指针,指针的指向就是成员函数的首地址。
这样的方法的确不错,节省了很多空间,但在对象中多了一个指针。所以我们最终的存储方式也并与按照此方法来。
方案3:仅仅存储成员变量。
计算机中的真实存储方式是按此而来的。不如我们来测一测:
#include<iostream>
using namespace std;
class A{
int i1;
int i2;
};
class B{
char c1;
double c2;
};
class C{
};
int main()
{
cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) << endl;
return 0;
}
得到A的结果是8,B的结果是16。(在VS2013的编译环境下进行测试)
那么这是怎么得到的呢?其实和结构体在内存中的存储方式完全相同。了解结构体内存对齐的盆友,点这里。
结构体内存对齐规则:
1.第一个成员变量的偏移地址为0;
2.其他成员变量要默认对齐到对齐数的整数倍地址处,对齐数=编译器默认大小和该成员变量之间的较小值。VS中编译器默认的对齐数大小是8,Linux中的默认值为4
3.结构体总大小必须是最大对齐数的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。
至于为何要内存对齐,描述内存对齐的博客中也有回答,感兴趣的朋友可以自行阅读。
而在测试的时候,不仅仅给了有成员变量的A和B,还给出了一个空类C。
那么C的大小是多少呢?答案是1。
为什么空类还要有大小???其实也很容易理解,我们实例化的过程就是创建一个对象的过程,我们不能只给名字,不给空间吧!否则就没有办法寻找它的存在了。但是这个空间给的太大又不合适,因为它占着空间实际上用处很少,所以最终给出了1。