目录
一、类的创建
C++中【class】为定义类的关键字,【{}】中为类的主体,注意类定义结束时后⾯分号不能省略。一般来说,类规范由两部分组成:
- 类的声明:数据成员的数据部分,成员函数的方法接口
- 类的方法定义:实现类的成员函数
【注意】
定义在类⾯的成员函数默认为inline
如果还有对内联函数不是很清楚的可以去看看这篇文章:C++内联函数简述——inline-优快云博客
//一个日期类
class Date
{
public:
//成员函数的方法接口
//打印日期
void print();
//设置日期类(后面会使用构造函数替代)
void set_date(int y, int m, int d);
private:
//数据成员的数据部分
int year;
int month;
int day;
};
二、访问权限
C++⼀种实现封装的⽅式,⽤类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限选择性的将其接⼝提供给外部的用户使⽤
C++中由三种访问限定符:
- public:成员在类外可以直接被访问
- private:成员在类外不能直接被访问
- protected:成员在类外不能直接被访问
【注意】
- 访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束
- ⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public
简单来说,类的声明提供了类的蓝图,而方法定义提供了细节
三、struct与class
可能会有眼尖的小伙伴发现了,C++中的类与C语言中的结构体好像是一样的(去除成员函数)。实际上,C++对C语言中的结构体进行了升级,使得结构体具有了与类相同的特性。类与结构体唯一的区别就在于:
- 结构体的默认访问类型是【public】,类的访问类型默认是【private】
C++对C语言中的结构体进行了升级,使得结构体具有了与类相同的特性:
结构体的默认访问类型是【public】,类的访问类型默认是【private】:
//struct定义类
struct Date1
{
int year;
int month;
int day;
//打印日期
void print()
{
cout << year << "-" << month << "-" << day << endl;
}
};
//class定义类
class Date2
{
int year;
int month;
int day;
//打印日期
void print()
{
cout << year << "-" << month << "-" << day << endl;
}
};
int main()
{
//struct
//结构体的默认访问类型是【public】
Date1 test1;
test1.year = 1;
test1.month = 1;
test1.day = 1;
test1.print();
//class
//类的访问类型默认是【private】
Date2 test2;
test2.year = 1;
test2.month = 1;
test2.day = 1;
test2.print();
return 0;
}
虽然C++对结构体进行了升级,使得结构体具有了与类相同的特性,但是C++程序员通常使用类来实现类的描述,而把结构体限制为只为表示纯粹的数据对象
四、类域
C++中类的设计要尽可能的将公有成员函数和实现细节分开。将成员函数的定义与声明分开体现了C++中的封装思想,就是将成员函数定义(源文件)与声明(头文件)放在不同的文件中
【test.h】头文件:
//一个日期类的声明
class Date
{
public:
//成员函数的方法接口
//打印日期
void print();
//设置日期类(后面会使用构造函数替代)
void set_date(int y, int m, int d);
private:
//数据成员的数据部分
int year;
int month;
int day;
};
【test.cpp】源文件:
#include "test.h"
//实现 Date 类中的成员函数
//打印日期
void Date::print()
{
cout << year << "-" << month << "-" << day << endl;
}
//设置日期
void Date::set_date(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
int main()
{
Date test;
test.set_date(1, 1, 1);
test.print();
}
类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤【 :: 】作⽤域操作符指明成员属于哪个类域
错误示范:
#include "test.h"
//实现 Date 类中的成员函数
//打印日期
void print()
{
cout << year << "-" << month << "-" << day << endl;
}
//设置日期
void set_date(int y, int m, int d)
{
year = y;
month = m;
day = d;
}
int main()
{
Date test;
test.set_date(1, 1, 1);
test.print();
}
原因是编译器会把这些函数视为全局函数,像【year】、【month】、【day】这样的变量函数首先会在局部域中寻找,然后去全局域中寻找
全局函数是不会进入到类域中去寻找变量的,只有指定类域函数才回去类域中去寻找
这里我们要明确一点,类的声明是不会开辟内存空间,只有在使用类创建对象时才会开辟内存空间
//一个日期类的声明
class Date
{
public:
//打印日期
void print();
//设置日期类(后面会使用构造函数替代)
void set_date(int y, int m, int d);
private:
//这里只是声明,不会开辟内存空间
int year;
int month;
int day;
};
int main()
{
//Date类实例化出对象d1和d2
//开辟Date类大小的内存空间
Date d1;
Date d2;
return 0;
}
因此像通过【类名】加【 :: 】作⽤域操作符去操作成员变量是不可以的
//一个日期类的声明
class Date
{
public:
//打印日期
void print();
//设置日期类(后面会使用构造函数替代)
void set_date(int y, int m, int d);
private:
//这里只是声明,不会开辟内存空间
int year;
int month;
int day;
};
int main()
{
//不存在成员变量的创建,会报错
Date::year = 1;
Date::month = 1;
Date::day = 1;
return 0;
}
五、类的大小
使用类创建的每一个新对象都有自己的存储空间,用于存储其内部变量和类成员。但是同一个类的所有对象共享同一组成员函数,即每一个成员函数只有一个副本
下面来计算一下【Date】类的大小:
//一个日期类
class Date
{
public:
//成员函数的方法接口
//打印日期
void print();
//设置日期类(后面会使用构造函数替代)
void set_date(int y, int m, int d);
private:
//数据成员的数据部分
int year;
int month;
int day;
};
int main()
{
cout << sizeof(Date) << endl;
return 0;
}
这里要注意一下,类的大小计算与结构体的大小计算一样,都要符合内存对齐规则
内存对齐的规则:
- 第⼀个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处
- 对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值
- 类的总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍
- 如果嵌套了类的情况,嵌套的类对⻬到⾃⼰的最⼤对⻬数的整数倍处,类的整体⼤⼩
- 就是所有最⼤对⻬数(含嵌套类的对⻬数)的整数倍
详细的计算可以看看这篇文章:C语言——自定义类型-优快云博客
那么这里就要考虑类中的成员函数是否也要计算大小?
先说结论: 类中的成员函数在计算大小时,不参与其计算
【Date】类中如果成员函数要参加类的大小计算话,首先一个【int】变量占4个字节,而成员函数的本质就是一个地址,我这里的环境是【32位】,因此一个地址占4个字节,那么根据内存对齐的规则来算【Date】类的总大小为:20字节
结果:
从结果来看【Date】类的大小为:12字节。其原因就是成员函数在计算大小时,不参与其计算,同一个类的所有对象共享同一组成员函数,即每一个成员函数只有一个副本
我们可以这样想,【Date】实例化【d1】和【d2】两个对象,【d1】和【d2】都有各⾃独⽴的成员变量存储各⾃的数据,但是【d1】和【d2】的成员函数指针却是⼀样的,如果⽤【Date】创建100个对象,那么成员函数指针就重复存储100次,太浪费了
那以下这两类的大小是多少呢?
//类1
class A
{
public:
void print()
{
cout << "hehe!" << endl;
}
};
//类2
class B
{
};
结果:
这里可能会有同学疑惑,不说成员函数不参与大小计算吗?为什么第一个会是1呢?而第二个类,里面明明什么都没有,为什么也会是1呢?
因为如果⼀个字节都不给,怎么表⽰对象存在过呢!我们的【&】运算符怎么取地址呢。所以 这⾥给1字节,纯粹是为了占位标识对象存在