目录
结构体在c语言和c++中的区别在于:c++中结构体不仅可以定义变量,也可以定义函数。举例c++方式实现简化版的栈:
- //c++方式实现简化版栈
- typedef int DataType;
- struct Stack
- {
- //定义函数:
- void Init(size_t capacity)
- {
- _array = (DataType*)malloc(sizeof(DataType) * capacity);
- if (nullptr == _array)
- {
- perror("Init::malloc");
- return;
- }
- _capacity = capacity;
- _size = 0;
- }
- void Push(const DataType& data)
- {
- //检查容量,判断是否需要扩容
- _array[_size] = data;
- _size++;
- }
- DataType Top()
- {
- return _array[_size - 1];
- }
- void Destroy()
- {
- if (_array)
- {
- free(_array);
- _array = nullptr;
- _capacity = 0;
- _size = 0;
- }
- }
-
- //定义变量:
- DataType* _array;
- size_t _capacity;
- size_t _size;
- };
- int main()
- {
- Stack s;
- s.Init(10);
- s.Push(2);
- s.Push(4);
- s.Push(6);
- s.Push(8);
- cout << s.Top() << endl;
- s.Destroy();
- return 0;
- }
上面结构体的定义在c++中更喜欢用class来代替。
- //类的定义
- class classname
- {
- //成员函数
- ……
-
- //成员变量
- ……
- };
class为定义类的关键字,classname为类的名字,{}里边为类的主体,注意{}后边紧跟着的封号‘;’不能省略。
类的主体中的内容包括两部分:成员变量(类的属性)和成员函数(类的方法)。
类的两种定义方式:
(1)、类的声明和类的定义全部放在类的主体中,需要注意的是:成员函数如果在类中定义,编译器可能会将其当做内联函数处理。
(2)、类的声明放在.h文件中,成员函数的定义放在.cpp文件中。注意成员函数名前要加上‘类名::’。
访问限定符
访问限定符说明:
(1)、public修饰的成员在类外可以直接被访问,protected和private修饰的成员在类外不能直接被访问,protected和private的作用类似
(2)、访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止,如果后面没有访问限定符,作用域就到类结束
(3)、class的默认访问权限为private,struct的默认访问权限是public(因为struct要兼容c)
注意:访问限定符只在编译时有用,当数据映射到内存后没有任何访问限定符上的区别
封装
c++将数据和操作数据的方法进行有机结合,通过访问限定符隐藏对象内部的实现细节,仅对外公开接口来和对象实现交互。封装的本质是一种管理,让用户更方便使用类。
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类外定义成成员时需要使用作用于操作符::指明成员属于哪个类域。
用类类型创建对象的过程称为实例化
(1)、类是用来对对象进行描述的一个模型一样的东西,限定了类有哪些成员。定义出一个类,并没有分配实际的内存空间;
(2)、一个类可以实例化出多个对象,实例化处的对象占用实际的物理空间,用来存储类成员变量;
类的主体包含成员函数和成员变量两部分,成员变量存放在公共代码段,所以一个类的大小实际上是该类中的所有成员变量大小之和,要注意内存对齐。对于空类,编译器给了一个字节来唯一标识这个类的对象。
结构体内存对齐规则:
(1)第一个成员对齐到0偏移量处,其他成员对齐到各自的对齐数的整数倍处。对齐数是编译器的默认对齐数与该成员类型大小的较小值,vs默认对齐数是8。
(2)结构体大小:最大对齐数(所有成员变量类型大小的最大值与编译器默认对齐数取较小值)的整数倍。
(3)如果存在嵌套结构体的情况,嵌套的结构体对齐到自己最大对齐数的整数倍处,结构体整体大小就是所有对齐数(含嵌套结构体的对齐数)中的最大对齐数的整数倍。
c++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作堆用户是透明的,即用户不需要手动传递,编译器自动完成。
(1)this指针的类型:类类型*const,即成员函数中,不能给this指针赋值。
(2)只能在“成员函数”的内部使用。
(3)this指针本质上是“成员函数的形参”,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
(4)this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递。
任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。默认成员函数:用户没有显示实现,编译器会自动生成的成员函数。
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象的整个生命周期内只调用一次。
- 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)对象实例化时编译器自动调用对应的构造函数。
(3)构造函数可以重载。即可以写多个构造函数,提供多种不同的初始化方案。
(4)如果类中没有显式定义构造函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义,编译器将不再自动生成。
(5)一般情况下都需要我们自己写构造函数来决定初始化方式。当成员变量全部是自定义类型时可以考虑不斜构造函数,编译器会调用自定义类型成员自己的默认构造函数。
(6)默认构造函数只能有一个。无参构造函数、全缺省构造函数、编译器默认生成的构造函数,都可以认为是默认构造函数。对于全缺省构造函数,若函数声明和函数定义分离,则缺省值只能卸载函数的声明中,函数的定义中不能写缺省值。
- //构造函数
- class Date
- {
- public:
- //无参构造函数
- Date()
- {
- _year = 2000;
- _month = 1;
- _day = 1;
- }
-
- //带参构造函数
- Date(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- //全缺省构造函数
- Date(int year = 2000, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
与构造函数功能相反,析构函数不是完成对对向本身的销毁,局部对象销毁工作是由编译器来完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
- class Stack
- {
- public:
- Stack(size_t capacity = 10)
- {
- _array = (int*)malloc(capacity * sizeof(int));
- if (!_array)
- {
- perror("Stack::malloc失败!");
- return;
- }
- _capacity = capacity;
- _size = 0;
- }
-
- //析构函数
- ~Stack()
- {
- if (_array)
- {
- free(_array);
- _array = NULL;
- _capacity = 0;
- _size = 0;
- }
- }
- private:
- int* _array;
- size_t _capacity;
- size_t _size;
- };
析构函数是特殊的成员函数,其特征如下:
(1)析构函数名是在类名前加上字符‘~’,无参数,无返回值类型。
(2)一个类只能有一个析构函数。若未显式定义,系统会生成默认的析构函数。注意:析构函数不能重载。
(3)对象生命周期结束时,C++编译器会自动调用析构函数。
(4)如果类中没有资源申请时,析构函数可以不写,直接使用编译器生成的默认析构函数;有资源申请时一定要写,否则会造成资源泄露。资源申请即类似malloc、calloc、realloc等的动态内存申请。
拷贝构造函数只有单个形参,该形参是对同类类型对象的引用(一般用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
- class Date
- {
- public:
- //拷贝构造函数
- Date(const Date& d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
拷贝构造函数也是特殊的成员函数,特征如下:
(1)拷贝构造函数是构造函数的一个重载形式。函数重载即函数名相同,形参不同。
(2)拷贝构造函数的参数只有一个且必须是类类型对象的引用,因为传值方式会引发无穷递归导致编译器报错。
(3)若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储的字节序完成拷贝,这种靠被叫做浅拷贝或值拷贝。浅拷贝即将对象中的所有内容(包括动态开辟的内存空间)原封不动的拷贝到新的对象中。注意:编译器生成的默认拷贝构造函数中,内置类型是按照字节序拷贝的,而自定义类型是调用自己的拷贝构造函数完成拷贝的。
(4)类中如果没有涉及资源申请时,拷贝构造函数是否显式定义都可以;一旦涉及资源申请时,则拷贝构造函数一定要写,否则就会造成浅拷贝。
(5)拷贝构造函数典型调用场景:用已存在对象创建新对象、函数传值传参、函数以值方式返回。为了提高程序效率,一般对象传参时,尽量使用引用类型,函数返回时根据实际场景能引用尽量使用引用。
c++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 + operator操作符 +(参数列表)
注意:
不能通过连接其他符号来创建新的操作符。比如:operator@
重载操作符必须有一个类类型参数。即运算符重载是针对自定义类型数据的
用于内置类型的运算符,其含义不能改变。例如:内置的整形+,不能改变其含义
作为类成员函数重载时,其形参个数看上去比操作符对应的操作数个数少一个,因为成员函数的第一个参数为隐藏的this指针
.* :: sizeof ? : . 以上5个运算符不能重载
(1)赋值运算符的重载格式
参数类型:const T&,引用传参可以提高传参效率
返回值类型:T&,返回引用也可以提高返回效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回*this:要符合连续赋值的含义
(2)赋值运算符只能重载成类的成员函数,不能重载成全局函数。因为如果没有显式实现,编译器会生成一个默认的赋值运,如果用户在类外自己实现一个全局的赋值运算符就会导致重载冲突。
(3)编译器生成的默认运算符重载是以值的方式椎子界拷贝。内置类型成员变量是直接赋值的,而自定义类型的成员变量需要调用对应类的赋值运算符完成赋值。
(4)如果类中未涉及到资源管理,赋值运算符是否显式实现都可以,一旦涉及资源管理则必须要显式实现。
- class Date
- {
- public:
- //赋值运算符重载
- Date& operator=(const Date& d)
- {
- if (this != &d)
- {
- _year = d._year;
- _month = d._month;
- _day = d._day;
- }
- return *this;
- }
- private:
- int _year;
- int _month;
- int _day;
- };