类的定义
class student
{
//主体 : 包括成员函数和成员变量
}; //注意分号
上面就是一个基本的类的定义, 当然还可以细分, 接下来我们继续看 :
类中的元素成为类的成员, 有两种 : 成员变量和成员函数
类的定义方式有两种 :
1. 声明和定义全部放在类中
2. 声明放在.h文件中, 类的定义放在.cpp文件中.
注意 : 成员函数如果在类中定义, 编译器可能会将其当做内联函数处理, 所以不推荐使用第一种方式
类的访问限定符与封装
类的访问限定符 :
public (公有), protected (保护), private(私有)
说明 :
- public修饰的成员可以在类外直接被访问
- protected和private成员在类外不能直接被访问
- 访问权限作用域从该限定符开始直接下一个访问限定符为止
- class的默认访问权限是private, struct的默认访问权限是public
类的封装 :
封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。
C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
类对象模型
如何计算类对象的大小?
只保存成员变量,成员函数存放在公共的代码段
成员变量的大小怎么计算呢? 根据结构体内存对其规则
结构体内存对齐规则 :
1. 第一个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的对齐数为8,gcc中的对齐数为4
3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
例如 :
struct A { char a; int b; char c; };
结构体A的大小为12
具体的计算过程为 :
注意 : 有一个特殊情况就是空类, 空类的大小不是0, 而是1. 编译器默认给了一个字节来表示空类
面试题 :
1. 结构体怎么对齐?
根据结构体内存对齐规则进行对齐
2. 为什么要进行内存对齐?
有两个原因 :
a.平台原因 :有的平台不能访问任意地址上的任意数据,有的硬件平台也只能在特定的地址取特定的数据,否则抛出异常
b.硬件原因: 经过内存对齐之后,CPU的内存访问速度大大提升。
3. 如何让结构体按照指定的对齐参数进行对齐?
#pragma pack(n)
4. 如何知道结构体中某个成员相对于结构体起始位置的偏移量?
#include <iostream> using namespace std; typedef struct Student { char* name; int age; int id; }Student; int main() { Student *s = nullptr; cout << (int)(&(s->name)) - (int)s << endl; cout << (int)(&(s->age)) - (int)s << endl; cout << (int)(&(s->id)) - (int)s << endl; system("pause"); return 0; }
5. 什么是大小端?
大端:低地址存高字节, 高地址存低字节.
小端:低地址存低字节, 高地址存高字节,
注意 : 无论是大端还是小端存储,计算机在内存中存放数据的顺序都是从低地址到高地址,不同的是取低字节还是高字节存在低地址里面
6. 如何测试某台机器是大端还是小端?
#include <iostream> using namespace std; int main() { unsigned int x = 0x12345678; if (*(char *)&x == 0x78) { cout << "小端" << endl; } else if (*(char *)&x == 0x12) { cout << "大端" << endl; } system("pause"); return 0; }
this指针
1. this指针的类型:类类型* const
2. 只能在“成员函数”的内部使用
3. this指针并不存在于对象中, 而是相当于成员函数的形参, 等于是把对象传给this指针.
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递
void Display()
{
cout << _year << endl;
}
//等价于
void Display(Date* this)
{
cout << this->_year << endl;
}
注意 : 除了构造函数外每个成员函数都有一个隐藏的指针形参而且是第一个形参 this ,
面试题 :
1. this指针存在哪里?
this指针存在栈上
2. this指针可以为空吗?
当this指针所在的成员函数没有指向当前对象并对其进行操作的时候可以为空, 比如 :
void test() { cout << "hello" << endl; }
当this指针所在的成员函数指向当前对象并且要对其进行操作的时候不能为空, 空指针引用, 会发生错误, 比如 :
void test() { // cout << _year << endl; cout << this->_year << endl; //对当前对象进行操作 cout << "hello" << endl; }
类的6个默认成员函数
即 : 构造函数, 析构函数, 拷贝构造函数, 赋值运算符重载, 取地址以及const取地址操作符重载
首先我们想一个问题, 如果我们构造一个空类, 比如 :
class Data {};
这个类里面什么也没有吗?
并不是 , 任何一个类在我们不显式定义的情况下, 都会生成6个默认的成员函数, 下面我们一起来看
1. 构造函数
1. 构造函数是一个特殊的成员函数, 名字与类名相同, 创建对象时由编译器自动调用, 在对象的生命周期内只调用一次, 构造函数的主要功能不是创建对象, 而是初始化对象
class Date { public: //无参构造函数 Date() {} //全缺省构造函数 Date() { _year = 1900; _month = 3; _day = 1; } //带参构造函数 Date(int year,int month,int day) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; };
特征如下 :
1. 函数名与类名相同
2. 没有返回值
3. 对象实例化时编译器自动调用对应的构造函数
4. 构造函数可以重载
5. 如果用户没有显式定义, 编译器会生成一个默认的
6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
2. 初始化列表
上述的代码虽然创建了一个对象之后成员变量也有值, 但是那个是在构造函数中通过赋值语句赋的值, 并不是初始化,那么怎样才能初始化成员变量呢, 那就是初始化列表
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式, 也可以达到给成员变量初始化的效果。
class Date { public: Date(int year, int month, int day) : _year(year) , _month(month) , _day(day) {} private: int _year; int _month; int _day; };
注意 :
1. 每个成员变量在初始化列表中只能出现一次
2. 类中包括引用成员变量, const成员变量, 类类型成员变量的必须放在初始化列表进行初始化
3. 尽量使用初始化列表初始化,因为对于自定义类型成员变量,一定会先使用初始化列表初始化。
4. 成员变量的声明顺序就是初始化顺序, 与初始化的先后顺序无关
3. exexplicit关键字
是修饰构造函数的关键字, 修饰之后将会禁止单参类型的隐式转换, 比如 :
不修饰 :
class Date { public: Date(int year) :_year(year) {} private: int _year; int _month; int _day; }; int main() { Date d(2018); d = 2019; //编译器会默认给2019构造一个无名对象, 然后再用无名对象给d赋值 return 0; }
修饰 :
就会报出一个错误, 禁止让它隐式类型转换
2. 析构函数
析构函数是特殊的成员函数
局部对象对象生命周期结束时, 编译器自动完成资源清理(并非销毁对象)
class A { public: A(int n = 10) { _data = new int[n]; //开辟空间 } ~A() { if (_data) { delete[] _data; //释放空间 _data = nullptr; 指针置位空 } } private: int* _data; };
特征如下 :
1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值。
3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
3. 拷贝构造函数
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
class Date { public: Date(int year,int month,int day) :_year(year) , _month(month) , _day(day) {} Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; };
特点
1. 拷贝构造是构造的一个重载形式
2. 拷贝构造的参数只有一个而且必须是引用传参, 否则会无穷调用
3. 如果不显式定义拷贝构造, 系统会默认生成一个拷贝构造函数 , 但是这种拷贝是字节拷贝, 也就是浅拷贝.
浅拷贝 : 将原来对象的引用直接赋给新对象, 也就是说新对象只是原对象的一个引用
深拷贝 : 开辟一个新的空间, 将原对象的值拷贝过来, 而不是原对象的引用.
4.赋值运算符重载
c++为了增强代码可读性引入了运算符重载
函数原型:返回值类型 operator操作符(参数列表)
Date& operator=(const Date& d) { if(this != &d) { _year = d._year; _month = d._month; _day = d._day; } }
注意 :
1. 重载操作符必须有一个类类型或者枚举类型的操作树
2. 用于内置类型的操作符, 不能改变含义, 如整形的 +
3. .* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。
4, 赋值运算符重载编译器默认生成的也是一个浅拷贝, 一般需要自己定义
const成员
在看取地址以及const取地址操作符重载这两个之前我们先来了解一下什么是const成员
const 修饰的类成员函数称为const 成员函数, 实际上const修饰的是隐含的this指针, 在该函数中不能对类的任何成员进行修改
比如 :
void display() const { cout << _year << endl; }
那编译器是怎样处理这个const的呢, 其实是这样的 :
void display(const Date *this) { cout << this->_year << endl; }
这样也就说明了后置 const 其实本质上是修饰隐含的 this指针
我们再来看一段代码 :
class Date { public: Date(int year = 2019,int month = 1,int day = 1) :_year(year) , _month(month) , _day(day) {} void display1() { cout << _year << endl; } void display() const { cout << _year << endl; } void print() const { display1(); //错误 } void print() { display(); // 正确 } private: int _year; int _month; int _day; }; int main() { Date d1; d1.display(); const Date d2; d2.display1(); system("pause"); return 0; }
结合以上代码思考下面几个问题并给出答案 :
1. const对象可以调用非const成员函数吗? 不可以
2. 非const对象可以调用const成员函数吗? 可以
3. const成员函数内可以调用其它的非const成员函数吗? 不可以
4. 非const成员函数内可以调用其它的const成员函数吗? 可以由此我们总结出 : 权限只能升高不能降低, 即非const的对象和成员函数可以调用const的成员函数, 但是const的对象和成员函数不能调用非const的成员函数
5 && 6. 取地址以及const 取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成, 也很少有特殊情况.
class Date { public: Date* operator&() { return this; } const Date* operator&() const { return this; } private: int _year; int _month; int _day; };
static成员
概念 :
static修饰的成员, 成为静态成员, 如果是成员变量一定要在类外进初始化
一个类类型的多个对象公用一个静态成员变量, 比如 :
class A { public: A() { ++_count; } static int count() { return _count; } private: static int _count; }; int A::_count = 0; void test() { A a; A b; cout << A::count() << endl; } int main() { cout << A::count() << endl; test(); system("pause"); return 0; }
运行结果为 :
注意 :1. static成员所有类对象共享
2. 必须在类外进行初始化
3. 访问有两种形式 就以上类来说 A::_count || a._count
4. 静态成员函数没有this指针, 不能访问任何非静态成员
问题 :
1. 静态成员函数可以调用非静态成员函数吗?
不能
2. 非静态成员函数可以调用类的静态成员函数吗?可以
与const成员类似, 还是权限的问题.
以上就是基本的类和对象的知识, 还有一些比如友元, 内部类, 继承, 多态 , 等问题后序会给出, 谢谢观看