C++学习笔记
C++
指针
1.悬空指针
定义:指针被释放后,还指向原来的内存空间。如:
1 | void *p = malloc(size); |
避免方法:
1 | void *p = malloc(size); |
2.野指针
定义:指针不确定其具体指向的内存空间。如:
1 | void *p; |
危害:可能指向任意内存段,因此它可能会损坏正常的数据,也有可能引发其他未知错误。
避免方法:
1 | void *p = NULL; |
重载
运算符重载
1 | 函数类型 operator运算符(形参列表) |
operator为关键字,专门用于定义运算符重载的函数。可以将 operator运算符 看成函数名称。
运算符重载规则
1.并不是所有的运算符都能被重载,如长度运算符sizeof、条件运算符: ?(三元运算符)、成员选择符.和域解析运算符::不能被重载。
2.重载不能改变运算符的优先级和结合性。
3.重载不改变运算符的用法(即不改变原先的使用规则)。
4.运算符重载函数不能有默认参数,因为这会改变运算符操作数个数。
5.运算符重载函数既可以作为类的成员函数,也可以作为全局函数。
将运算符重载函数作为类的成员函数时,二元运算符的参数只有一个,一元运算符不需要参数。之所以少一个参数,是因为这个参数是隐含的。当类的对象调用重载后的运算符时,此对象就隐形作为一个参数了。
将运算符重载函数作为全局函数时,二元操作符就需要两个参数,一元操作符需要一个参数,而且其中必须有一个参数是对象,好让编译器区分这是程序员自定义的运算符,防止程序员修改用于内置类型的运算符的性质。
6.箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载。
流操作符重载
输入流操作符>>和输出流操作符<<。
cout是ostream类对象,cin是iostream类对象。
1 | //输入流操作符>>重载 |
下标运算符[]重载
只能以成员函数的形式进行重载,声明格式一般为:
1 | //第一种方式:不仅可以访问元素,还可以修改元素。 |
可以通过重载[]来实现变长数组。
1 | class Array{ |
操作符重载
(1)流操作符重载
必须作为友元函数(关键字:friend)或普通全局函数来重载。
1 | std:ostream& operator<<(std::ostream& os,const className& object){ |
(2)一元运算符重载(++、–、-、!)
前缀形式重载调用 operator ++ () ,后缀形式重载调用 operator ++ (int)
1 | // 重载前缀递增运算符( ++ ) |
(3)二元运算符重载(+、-、*、/)
类成员函数或全局函数
1 | // 类成员函数重载 + 运算符,用于把两个 Box 对象相加 |
(4)赋值运算符
- 参数类型:引用传参,用const修饰,即const 类&
引用传参可以提高传参效率。
1 | void operator=(const Distance &D ) |
返回值类型:引用返回,即 类&
引用返回可以提高返回的效率,有返回值目的是为了支持连续赋值功能。1
2
3
4
5
6
7Data operator=(const Data& d)//可以提高效率
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}要检查是否给自己赋值
1 | Data operator=(const Data& d) |
返回*this:要符合连续赋值的含义。
1
2
3
4
5
6
7Data& operator=(const Data& d)//用引用返回,可以提高效率,减少拷贝
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
(5)函数调用运算符()重载
函数调用运算符 () 可以被重载用于类的对象。当重载 () 时,您不是创造了一种新的调用函数的方式,相反地,这是创建一个可以传递任意数目参数的运算符函数。
1 | // 重载函数调用运算符 |
(6)下标运算符[]重载
下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。
1 | int& operator[](int i) |
(7)类成员访问运算符->重载
运算符 -> 通常与指针引用运算符 * 结合使用,用于实现”智能指针”的功能。
语句 p->m 被解释为 (p.operator->())->m
1 | Obj* operator->() const |
引用
引用必须在声明时初始化,初始化后无法改变指向。
左值、右值的区别:
左值:可以取地址的对象
右值:不可以取地址的对象(如常量、表达式、函数返回值)
左值引用
就是对左值进行引用
1 | // 1.左值引用只能引用左值 |
右值引用
就是对右值进行引用
1 | // 1.右值引用只能引用右值 |
深拷贝与浅拷贝
浅拷贝是创建一个新对象,新对象和原对象共享同一个底层资源,简单的赋值拷贝。浅拷贝在拷贝后对象共享同一份底层资源,可以提高效率,但是当对象析构时可能会出现不确定的行为,因为资源会被重复释放。
深拷贝则是创建一个新对象,,在堆中重新分配空间,新对象拥有原对象的全部资源,二者之间互不影响。深拷贝则会为每个对象创建独立的底层资源,避免了这个问题,但是会占用更多的内存。
当对象中有指针指向动态分配的内存时,为了安全地复制对象,需要显式地实现深拷贝,通常通过重载类的拷贝构造函数和赋值操作符来完成
类与结构体
C语言中,结构体只是用来封装不同数据类型的数据,没有构造函数和成员函数。
C++中,结构体除了默认权限和继承默认权限不一样外,其他功能与类一样。(结构体默认public,类默认private)
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。
继承
派生类可以访问基类中所有的非私有成员。因此基类成员如果不想被派生类的成员函数访问,则应在基类中声明为 private。
一个派生类继承了所有的基类方法,但下列情况除外:
- 基类的构造函数、析构函数和拷贝构造函数。
- 基类的重载运算符。
- 基类的友元函数。
多态
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
编译时多态、静态多态(静态链接或早绑定):函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了,如函数重载和运算符重载
运行时多态、动态多态(动态链接或后期绑定):根据所调用的对象类型来选择调用的函数,如派生类中的虚函数重写
虚函数与纯虚函数
1 | //虚函数 |
当类中有虚函数时,会为该类生成一个虚函数指针表(虚函数表),同时为该类添加一个虚函数表指针成员(用于访问虚函数表),表中包含一个或多个函数指针,指向该类的虚函数地址
数据抽象
只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。
优势:
- 类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
- 类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
数据封装
把数据和操作数据的函数捆绑在一起,通过将数据和操作数据的函数封装在一个类中来实现。
访问修饰符
- private: 私有成员只能在类的内部访问,不能被类的外部代码直接访问。
- public: 公有成员可以被类的外部代码直接访问。
- protected: 受保护成员可以被类和其派生类访问。
优点:
- 数据隐藏: 通过将数据成员声明为私有,防止外部代码直接访问这些数据。
- 提高代码可维护性: 提供公共方法来访问和修改数据,这使得可以在不影响外部代码的情况下修改类的内部实现。
- 增强安全性: 防止不合法的数据输入和不当的修改操作。
- 实现抽象: 提供了一种机制,使得用户不需要了解类的内部实现细节,只需要了解如何使用类的公共接口即可。
接口(抽象类ABC)
类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 “= 0” 来指定的。C++的接口是通过抽象类来实现的,抽象类不能被用于实例化对象,它只能作为接口使用。
如果一个 ABC 的子类需要被实例化,则必须实现每个纯虚函数,这也意味着 C++ 支持使用 ABC 声明接口。如果没有在派生类中重写纯虚函数,就尝试实例化该类的对象,会导致编译错误。可用于实例化对象的类被称为具体类。
构造函数类
按参数区分:有参、无参(默认构造函数)
按照类型区分:普通、拷贝(或复制构造)
拷贝构造调用情景:
1.对象以值的形式作为函数参数
2.对象以值的形式作为函数返回值
3.将一个对象用于给另一对象进行初始化时
1 | class Person{ |
