在C++中,在原来C语言基础上引入了类的概念。与C语言最大的不同就是:C++可以在类中定义函数。由类声明的变量,称为对象。
1.类的定义
1.1类定义的格式
class为定义类的关键字,Stack为类的名字,{}中为类的主体,(类定义结束时后面分号不能省略)类体中内容称为类的成员:类中的变量称为成员变量,类中的函数称为类的方法或成员函数。
class stack
{
public://成员函数void init(int n = 4){array = (int*)malloc(sizeof(int) * n);if (array == nullptr){perror("malloc申请空间失败");return;}capacity = n;top = 0;}void push(int x){top = top + 1;array[top] = x;}int top(){//assert(top > 0);return array[top - 1];}void destory(){free(array);array = nullptr;top = capacity = 0;}private://成员变量int* array;int capacity;int top;
};
为了区分成员变量,一般会习惯成员变量加上一个特殊标识,如成员前面或后面加上_或m开头。
注意C++中这个并不是强制的,只是一些惯例,具体看公司要求。
class Date
{
public://为了区分成员变量,一般习惯将成员变量加一个特殊标识,如_或mvoid Init(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};
注:
C++兼容C语言,可以利用C语言的结构体关键字struct来定义类。
C++升级struct升级成了类
1.类里可以定义函数
2.struct名称就可以代表类型
struct Date
{int a;int b;};
int main()
{Date d1;struct Date d2;//这两种表述都对,不需要再typedefreturn 0;
}
1.2访问限定符
C++用一种实现封装的方式,用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限,选择性的将其接口提供给外部的用户使用。
1.public修饰的成员在类外可以被直接访问
2.protected和private修饰的成员在类外不能被直接访问
3.访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域就到}结束
4.class定义成员没有被访问限定符修饰时默认为private,struct默认为public
5.一般成员变量都会被限制为private/protected,需要给别人使用的成员函数会改为public
1.3类域
类定义了一个新的作用域,类的所有成员都在类的作用域中,在类体外定义成员是,需要使用::作用域操作符指明成员属于哪个类域
class Stack
{
public://成员函数void Init(int n = 4);private://成员变量int* array;int top;int capacity;};void Stack::Init(int n )//声明和定义分离,需要指定类域{array = (int*)malloc(sizeof(int) * n);if (array == nullptr){perror("malloc申请空间失败");return;}capacity = n;top = 0;
}
类域影响的是编译的查找规则,上面程序中Init如果不指定类域Stack,那么编译器就把init当成全局函数,那么编译时找不到array等成员的声明/定义在哪,就会报错。
指定类域Stack,就知道Init是成员函数。当前域找不到的array等成员,就会到类域中去查找。
2.实例化
2.1概念
1.用类类型在物理内存中创建对象的过程,称为类实例化出对象。
2.类是对象进行一种 抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只是声明,没有分配空间,用类实例化出对象时,才会分配空间
3.一个类可以实例化出多个对象,实例化出的对象占用实际的物理内存,存储类成员变量
class Date { public://为了区分成员变量,一般习惯将成员变量加一个特殊标识,如_或mvoid Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private://这里只是声明,没有开空间int _year;int _month;int _day; };int main() {Date d1;//Date类 实例化出对象d1d1.Init(2025, 1, 21);d1.Print();return 0;}
2.2对象的大小
类对象中有哪些成员呢?
类实例化出的每个对象,都有独立的数据空间,所以对象中肯定包含成员变量,那么成员函数是否包含呢?
首先函数被编译后是一段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。那么,有没有存指针的必要呢?Date实例化d1和d2两个对象,它俩都有各自独立的成员变量year/month/day存储各自的数据,但是d1和d2的成员函数init和print指针确是一样的,存储在对象中就浪费了。如果实例化100个对象,那么成员函数指针存储100次就太浪费了。
注:函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令(call指令),编译器在编译链接时就要找到函数的地址,不是在运行时找。
所以分析得到对象中只存储成员变量,C++规定实例化的对象也要符合内存对齐的规则。
#include<iostream>
using namespace std;class A
{
public:void Print(){cout << ch << endl;}private:char ch;int i;
};int main()
{A a;cout << sizeof(a) << endl;return 0;
}
我们再来研究一下空类
#include<iostream>
using namespace std;class B
{
public:void Print(){//}
};class C
{};int main()
{B b;C c;cout << sizeof(b) << endl;cout << sizeof(c) << endl;return 0;
}
为什么没有成员变量的B和C类对象的大小是1呢?
因为如果一个字节不给,怎么表示对象存在过呢!这里的1字节,纯粹是为了占位标识对象存在。
3.this指针
首先先来看一段代码
#include<iostream>
using namespace std;class Date
{
public:void Init(int year, int month, int day){_year=year;_month=month;_day=day;}void Print(){cout << _year << "/" << _month << "/" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1, d2;d1.Init(2025, 1, 21);d2.Init(2025, 1, 22);d1.Print();d2.Print();return 0;
}
这段代码中,Date类中有Init和Print两个成员函数,函数体中没有关于不同对象的区分,那么当d1调用Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?
C++中一个隐含的this指针解决这个问题。
1.编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的指针,叫做this指针。
Date类的Init的真实原型为
void Init(Date* const this,int year,int month,int day)
2.类的成员函数中访问成员变量本质都是通过this指针访问的
_year=year;//this->_year=year
3.C++规定不能在形参和实参的位置显示的写this指针,编译时编译器会处理,但是可以在函数体内显示使用this指针
实际上的代码
d1.Init(&d1,2025,1,21)
4.两道代码题
题目1.
下面程序的运行结果是 编译报错 运行崩溃 还是 正常运行?
#include<iostream>
using namespace std;class A
{
public:void Print(){cout << "A::Print()" << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
答案是正常运行
对空指针解引用不是会发生运行崩溃吗?首先我们得明白,成员函数并不存放在类对象中,而是存放在公共代码段。表面上看起来是对空指针解引用,其实编译器不需要通过解引用去找对应函数,只需要去公共代码区执行对应函数即可。
空指针没有解引用就不会报错,所以答案是正常运行。
题目2.
下面程序的运行结果是 编译报错 运行崩溃 还是 正常运行?
#include<iostream>
using namespace std;class A
{
public:void Print(){cout << "A::Print()" << endl;cout << _a << endl;}
private:int _a;
};int main()
{A* p = nullptr;p->Print();return 0;
}
答案是运行崩溃
这里我们访问了对应的成员变量,会传递对应对象的地址,会对空指针进行解引用,所以会崩溃。