基础知识复习
宏
- 无参宏
- 有参宏 (带有缺陷)
#define MAX(a,b) a>b?a:b
int main()
{
int n1=1,n2=0;
int n3 = MAX(n1 , n2); // n3 = 1
// 在有参宏中使用自增运算符的时候, 就导致了
// 逻辑上的错误. 且无法避免
int n4 = MAX(n1++,n2++);// n4 = 2,n1=3, n2=1
}
inline
内联函数
语法: 在函数名前加上inline
关键字, 这个函数就有可能成为内联函数.
成为内联函数的标准:- 函数题必须足够小(代码行数少)
- 函数内部不能有复杂的逻辑(switch,函数递归,各种嵌套的循环或选择结构都是不行的)
- 用法和函数一样
- 功能和有参宏差不多(直接将函数调用替换为函数体的代码.) ```c inline int max(int a , int b){ return a > b ? a : b; }
int main() { int n1=1,n2=2; // 正常方式调用函数 // 但是在编译之后, 这里的函数调用会替换成函数体 // 就类似于有参宏在预编译之后会被直接替换一样. // 但是如果实参是表达式的时候,会先计算出表达式的 // 结果, 然后再进行替换(而有参宏是直接拿表达式来 // 替换) int n3 = max(n1++,n2++); }
3. `const`
<br />`const`是一个常量修饰符, 在C++中常量的一些使用规则:
1. 常量必须被初始化
1. 在使用常量的时候, 编译器会直接将常量名用它的初始值来替换(和无参宏一样. 在源码中使用宏的名字,经过预处理之后会被替换成宏的内容)
1. 使用场合
1. 修饰指针
1. 常量指针 : 将指针指向的内容修饰为常量
```c
char szBuff[100]={"hello"};
szBuff[0] = 'A'; // 可以修改
// p就是常量指针
// 1. 指向的内容被修饰为常量,因此,不能通过p去修改内存.
// 2. p自身是可以被修改的
// 3. const只是在语法层面上限制一个指针不能修改它指向的内容, 至于指针指向的内容能不能被修改是不受const的改变.
const char* p ; // 可以不用初始化
p = szBuff;
const char* p1 = "hahahah";
szBuff[0] = 'B'; // 仍然可以被修改
p[0] = 'A' ; // 语法报错.
p1[0] = 'A'; // 语法报错
1. 指针常量: 将指针自身修饰为常量
char szBuff[100]={"hello"};
// 定义一个指针常量p, 指针常量必须被初始化.
char* p const = szBuff;
// 1. 指针不能再指向其它内存
p = "hello" ; // 错误, p只能指向szBuff
p[0] = 'A'; //在语法上是通过.
char* p2 const = "hahah";
p2[0] = 'A'; // 在语法上是通过.在运行时就错误.因为p2保存的是一个常量字符串的地址, 常量字符串不能修改.
1. 定义指针时 ,`*`在 `const`的左边, 指针是一个指针常量
1. 定义指针时 ,`*`在 `const`的右边, 指针是一个常量指针
1. 修饰引用
int n = 100;
// 定义一个引用.
int& rNum = n;
rNum = 10; // 实际就是在修改变量n
// 常量引用.
// 不能rNum2去修改了.
const int& rNum2 = n;
rNum2 = 20; // 报语法错误
1. 修饰成员函数
实际修饰的是this
指针,也就是将this
指针修饰成常量指针
class MyClass{
int m_nNum;
public:
void fun() {
// m_nNum = 100;
}
};
void fun(const MyClass* pObj){
pObj->fun(); // 报语法错误.
// 原因分析:
// 1. pObj是一个常量指针
// 2. 成员函数有条件修改自身的成员变量, 这样相当于通过指针调用了成员函数, 成员函数在内部修改了成员, 实际就相当于间接通过指针修改了指针指向的内存. 这是常量指针所不允许的.
}
1. <br />修改版;
class MyClass{
int m_nNum;
public:
// const 修饰的是this指针.
// this 就相当于 const MyClass* this;
void fun() const {
this->m_nNum = 100; // 此处会报语法错误.
}
};
void fun(const MyClass* pObj){
pObj->fun(); // 不会报错
}
1. <br />总结:
1. 在成员函数后加上`const` , 成员函数就是一个常量成员函数, 在这样的函数内部不能修改自身的成员变量, 也不能调用其它的非常量成员函数
1. 通过常量对象指针(`const MyClass* pObj`), 只能调用常量成员函数.
引用
性质:- 引用类型的变量不占用内存空间. 其内存空间来自被引用的变量.
- 定义的时候必须初始化
- 不能再引用其它的变量
- 修改自身的时候, 被引用的变量也会被修改,被引用的变量被修改了, 引用类型的变量也会被修改, 因为它们使用是同一块内存空间.
使用场合
传参
void swap(int& l, inr& r){
int t = l;
l=r;
r =t;
}
int main(){
int n1=10,n2=20;
swap(n1,n2); // 执行之后, n1=20,n2=10
}
传递函数返回值
int& fun(int& n){
return n;
}
int main()
{
int nNum = 100;
fun(nNum) = 200; // 1. 修改了哪块内存?(nNum)
int n2 = fun(nNum);
n2 = 10; // nNum有被修改吗? 没有. 因为n2不是引用类型的变量, 它拥有自己的内存空间. 所以修改了n2不会影响nNum
int& rNum = fun(nNum);
rNum = 10; // 修改到nNum的内存空间了. 因为rNum是一个引用, 没有独立的内存空间,其内存空间使用的就是nNum的.
}
- 引用类型的变量不占用内存空间. 其内存空间来自被引用的变量.
类型转换
const_cast:常量类型转换 • 去除常量类型
void fun(const char* pStr){
pStr[0] = 'A'; //语法报错.
// 1. C语言风格
char* p = (char*)pStr;
p[0] = 'A'; // 不会报错
// 2. c++风格
char* p2 = const_cast<char*>(pStr);// 类似于(char*)pStr
}
int main()
{
char buff[100];
fun(buff);
}
static_cast:静态类型转换 •编译器认可的,例如char转为int ```c void fun(int n){}
int main()
{
// 编译器会将3.14从double类型隐式转换成int类型再传参
fun( 3.14 ) ; // 语法能通过,但是会报一个警告:精度丢失…. // 1. C语言风格
fun( (int) 3.14 );// 强制转换成和实参一样的类型
// 2. c++
fun( static_cast
1. reinterpret_cast:强制类型转换 •编译器不认可的,例如int*转为int
<br />用于编译器无法进行隐式转换时的类型转换.
```c
int main()
{
int* p = 0x403000; // 语法报错.类型不匹配
// C语言风格
p = (int*)0x403000;
//c++
p = reinterpret_cast<int*>(0x403000);
}
- dynamic_cast:动态类型转换,用于将父类和子类的指针或引用进行转换的(继承的时候会用到,能够将基类转换为派生类) ```c class Base{
}; class MyClass : public Base{ int nNum[100]; }; class B : public Base{ int m_nNum; };
int main() { Base obj = new B; MyClass pObj = obj; // 语法报错.
// 可以在语法上通过.
// 但在运行的时候会出现严重问题. 因为obj
// 本质上并非是MyClass类型,而是B类型.
pObj = (MyClass*) obj;
// dynamic_cast可以检测是否能够转换
// 如果不能就返回nullptr
pObj = dynamic_cast<MyClass*>(obj);
}
<a name="a2e6ee0b"></a>
# 面向对象-类
类的组成:
1. 成员变量
1. 一个对象的数据组成. 一个对象的内存空间就是由成员变量组成.
1. 成员在类中的声明顺序决定了成员变量在内存中的顺序.
1. 使用成员变量
1. 都需要提供一个对象, 才能使用成员变量.
1. 在成员函数内部, this指针来表示当前对象
```c
public:
void fun() {
// this也可以不写,
this->m_nNum1 = 10;
// 不写的时候编译器会自动加上.
m_nNum2 = 100;
}
1. 在类的外部, 只能通过对象变量来访问成员变量
int main()
{
MyClass obj;
// 必须通过对象来使用成员变量
obj.m_nNum2 = 10;
}
成员函数
- 成员函数内部会自带一个
this
指针. 这个指针从哪来的?- 通过对象调用函数的时候c++会自动将对象的内存首地址赋值给成员函数内部的this指针.这样一来, 成员函数被调用之后this指针就保存着调用了这个成员函数的对象变量.
- 普通成员函数
- 需要通过对象才能调用.
- 通过this指针也能调用.
- 其它情况和普通函数一样. 也可以设置默认参数, 也可以进行函数重载.
构造函数
- 构造函数是一个特殊的成员函数. 它的作用是用于构造一个对象. 它没有返回值, 因为返回值默认就是一个类对象.
构造函数只能被自动调用.
定义变量时(定义局部变量, 全局变量)
MyClass g_obj ; // 调用构造函数
int main()
{
MyClass obj1; // 调用构造函数
}
从堆空间申请对象时
int main()
{
MyClass* pObj = new MyClass;//调用构造函数
}
函数形参
void fun(MyClass obj){}
int main(){
MyClass obj1; // 调用了构造函数
fun( obj1 );// 会为形参obj调用构造函数.
fun( obj1 );// 会为形参obj调用构造函数.
}
函数返回值
MyClass fun(){
MyClass obj;
return obj;
}
int main( ){
MyClass obj2 ;
MyClass obj3 = obj2 ;
MyClass obj1 = fun(); // 给obj1调用构造函数
}
构造函数可以进行重载
- 当构造函数没有进行重载的时候,只能调用无参的构造函数. 无参构造函数是C++编译器默认提供的一个构造函数(不需要定义就有了), 但如果定义了其它版本的构造函数, 编译器就不再提供无参构造.
- 再进行构造函数重载之后, 构造对象的时候, 就可以通过传参来决定调用哪个重载版本 ```c class MyClass{ public: /重载了构造函数/ MyClass(){} MyClass(int n){} MyClass(double d){} MyClass(int n , double d){} MyClass(MyClass& obj ){} }
- 成员函数内部会自带一个
void fun(MyClass obj){}
MyClsas fun2(){ return 2; }
int main(){ // 会为obj11调用转换构造函数. // MyClss obj11(2); MyClss obj11 = fun2();
// 隐式转换:
// C++编译器会尝试将整型的实参5,转换成MyClass类型的形参. 但C++编译器是不能没有任何依据地转换.规则: 形参是类类型,并且具有一个构造函数刚好可以将实参传递进去. 此时编译器就可以将实参传给形参的构造函数,直接构造出形参, 而不是直接将实参赋值给形参.
fun( 5 ); // 不会报错, 且会给形参obj调用构造函数: MyClass(int n){}
// 在构造对象时, 会根据实参来调用不同版本的构造函数.
MyClass obj1(5);// MyClass(int n){}
fun( obj1 );// 给形参obj调用了构造函数, 选择的版本:MyClass(MyClass& obj ){}
MyClass *p = new MyClass(5,1);// MyClass(int n , double d){}
MyClass obj2(obj1);// MyClass(MyClass& obj ){}
} ```
1. 构造函数的一些术语:
1. 默认构造 : 指的是没有形参的构造函数, 由编译器默认提供, 在某些场合编译器需要自动调用一个类对象的构造函数时, 只能调用默认构造.例如: 子类继承了父类,当子类对象被构造的时候, 父类的构造也会被自动调用,此时就只能自动调用父类的默认构造
1. 转换构造 : 指的是那些只有一个形参,且参数类型是非本类类型的构造函数们. 一般能够显式调用(例如 `MyClass obj(5)`), 也能隐式调用: `fun(5);` fun的形参是`MyClass`类型
1. 拷贝构造: 指的是只有一个形参, 且参数类型是本类类型的引用. 一般是在定义一个对象的时候, 将另一个对象作为初始值时,就会自动调用这个版本的构造函数, 一般编译器会默认提供一个拷贝构造, 默认提供的拷贝构造会将对象的内存空间进行拷贝.
类中包含有指针成员的时候, 一般就需要自己编写拷贝构造, 实现对指针指向的内存进行拷贝的功能. 否则默认拷贝构造是不会去拷贝指针指向的内容. (深拷贝和浅拷贝的区别)
1. 带参构造 : 含有两个以上的形参的构造函数统称带参构造.
- 析构函数
- 作用和构造函数相反, 当一个对象被销毁的时候,就会调用析构函数.
- 运算符重载函数
- 访问控制
- public : 类内类外都能访问.
- protected : 控制在子类内部能访问, 在类外不能访问.
- private : 在子类和类外都不能访问.
- 静态成员
- 友元