C到C++的过渡
1.const的使用
- const的定义
- C:变量,不能改变值的变量
- C++: 常量,有类型描述符的常量
- 使用:
- 数组的使用: 数组长度的要求:常量、整型
- const修饰普通类型的变量、修饰指针
2.1 修饰普通类型变量的时候:const int a =10;
编译时把所有对 a的直接使用全部换成10;
2.2 const修饰的是指针:常量指针和指针常量
常量指针:const int *p1;
*p1被const修饰,*p1不能被赋值,p1可以随便改
指针常量:int * const p2;
p2被const修饰,p2不能被赋值,只能且必须初始化
2.define的使用
2.1. define的定义
编译预处理指令,用一个标识符来表示一长串东西
#define HAHAHA "我是这个世界上最可爱的人大大A"+1234342
什么时候起作用:编译器编译的时候做宏替换,把宏替换成宏表示的表达式
缺点:只做编译时替换,不做安全检查printf(HAHAHA);
2.2 const和define的联系
- 无参宏define和const的使用:
#define NUM 100
printf("%d", NUM);// printf("%d", 100);
// 必须初始化
const int a = 10;
// 初始化后不能被重新赋值
//a = 20;
*(int*)&a = 20;
printf("%d", a);
// 和下面相同
printf("%d", 10);
- 用const替代define的原因:
- const是有类型描述的常量,有类型描述就有安全检查,define是无类型值,只替换不检查
- const修饰的变量有内存,define没有
- 生命周期:const修饰的值的生命周期取决于值的作用域
- 局部const,生命周期局部
- 全局 const,生命周期全局
- define的生命周期:从#define定义位置开始,到文件末尾或#undef
2.3 define和enum
enum SEASON {
// 默认从0开始,后面的值是前面的值+1
spring=9,summer=10,autumn,winter
};
#define SPRING 9
#define SUMMER 10
#define AUTUMN 11
#define WINTER 12
- 宏不能限制函数参数传递,而enum是带类型的,有安全检查,会检查参数传递,只能传enum的成员,不允许传其他值
2.4 inline函数和define
// 有参宏
#define MAX(a,b) a>b?a:b
// 内联函数
inline int Max(int a, int b) {
return a > b ? a : b;
}
int main()
{
int a = 10, b = 20;
int c = MAX(a, b);
// 编译时替换成:
int c1 = a > b ? a : b;
// 结果不确定 a大于b a加两次
int d = MAX(a++, b);
// 编译时替换成
int d1 = a++ > b ? a++ : b;
int d2 = Max(a++, b);
return 0;
}
3.内存申请和释放
// C:
// 返回的是无类型的指针
int *p2;
int *p = (int*)malloc(100*sizeof(int));
ZeroMemory(p, 100 * sizeof(int));
free(p);
p = nullptr;
// c++
// new 什么类型的内存[多少份]
// 返回类型取决于new的类型
// 可以申请的时候同时初始化
int *p3 = new int[100]{1,2,3,4,5};
delete[] p3;
// delete什么时候带[]什么时候不带[]->看new有没有[]
int *p4 = new int(5);
delete p4;
int *p5 = new int[100];
delete[] p5;
- malloc/free 和 new/delete的比较
- malloc申请的内存是无类型的,需要自己强转,new申请的内存取决于new的类型
- mallo申请的内存的时候不支持初始化,new支持,如果new的是单个的 new int(1),用()初始化,如果new多个 new int[3]{1,2,3},y用{}初始化
- 内存释放的时候,malloc申请的内存直接用free释放,new申请的内存用delete或delete[]释放,带不带[]取决于new带不带[]
- malloc/free能调用构造/析构函数吗?不行。而new/delete支持调用构造/析构函数
4.函数重载
4.1 函数重载要求
- 函数名相同
- 函数返回值类型无要求
- 参数不同:类型、个数、顺序3者满足其一即可
4.2 函数重载的本质
名称粉碎机制 避免extern "C"
[图片上传失败...(image-f2485d-1538980069964)]
4.3 为什么不能通过返回值类型来达到重载
当不用函数返回值,单纯调用的时候没办法区分调用哪个函数
5.函数默认参数
5.1 默认参数的赋值顺序
- 只能从右到左,中间不允许隔断:只要左边的形参有默认参数,该形参右边的参数必须也有默认参数
void Fun(int a = 10,int b=20) {
printf("%d", a);
}
5.2 带默认参数的函数与重载函数的冲突
void Fun(int a = 10,int b=20) {
printf("%d", a);
}
void Fun() {
printf("void\n");
}
int main()
{
// Fun(10,20);
// Fun();
Fun();
return 0;
}
5.3 默认参数是给函数声明还是函数定义
- 声明和定义只能写1个,要么写在声明里,要么写在定义里
- 不能既写在声明里又写在定义里
- 推荐大家写在声明里
void Fun(int a, int b);
void Fun(int a = 10,int b=20) {
printf("%d", a);
}
void Fun1(int a = 10);
void Fun1(int a) {
printf("%d",a);
}
// error!
void Fun2(int a = 10);
void Fun2(int a = 10) {
printf("%d", a);
}
6.引用
int a = 10;
int c = 20;
// 定义a的引用b
// 引用的定义和const一样,必须初始化
// 初始化后对b的使用都是对a的操作
// 引用是从一而终的,不能再引用别人
int &b = a;
// 下面这个是取地址,不是让b变为c的引用
&b = c;
// 什么时候表示引用,什么时候表示取地址
// 当&和类型在一起的时候表示引用
// 当&和变量在一起的时候表示取地址
6.1 引用的定义
- 引用:变量的别名,有内存吗,不单独占据内存,和被引用的变量共用一块内存,一经初始化,不能重新引用别人
6.2 引用的使用
void MySwap(int &nNumA,int &nNumB) {
int temp = nNumA;
nNumA = nNumB;
nNumB = temp;
}
// 引用的使用
int nNumA = 10, nNumB = 20;
MySwap(nNumA, nNumB);
const int nNumC = 0;
const int &a = nNumA;
const int &b = nNumC;// nNumC只被读不能写,b要的是读写权限,不能给
int &c = nNumC;
c = 100;
7.类型转换
7.1 C的类型转换
- 强制类型转换以及隐式类型转换
- 强制类型转换;编译器不支持的,类型不通:
int a =10;
int *p = &a;
int nAddr = (int)p;
- 隐式类型转换:编译器支持的默认类型转换->小转大
int nNumA = 'a';
7.2 C++的类型转换
const_cast<>;
reinterpret_cast<>;
static_cast<>;
dynamic_cast<>;
8.c++的输入输出
- cin 和 cout 自带类型识别 不需要格式控制符
-
格式化输入输出:设置宽度setw、填充字符setfill、进制输出hex/dec/oct。。。
类
- 类的定义格式
class XXX{
// 成员变量以及成员函数
} - 类的大小
空类:1字节
正常的:内存对齐,默认对齐格式(类成员中最大基本数据类型的大小)
#include
#include
using namespace std;
class A {
char c;
double a;
char b;
};
int main() {
cout << sizeof(A);
return 0;
}
- 类的访问权限控制符
public/protected/private - 类的成员变量和成员函数
在制定权限控制符下和定义普通变量/函数相同 - this指针
不是固定值、不是成员变量、在成员函数内使用,是调用该成员函数的类对象的地址 - 类的成员函数的三种实现方式
类内、类外、不同文件
命名空间
- 命名空间的定义
namespace XXX{}
是作用域,不是变量类型,只能在全局范围内定义 - 命名空间的命名规则必须符合标识符命名规范
- 命名空间是作用域,不能像类一样有权限限制,默认是public
- 命名空间可以嵌套定义其他类型
- 不同文件内可以定义相同名称的命名空间,但是两个文件的命名空间内不能有同名类型/变量
- 命名空间的作用:避免命名冲突,命名空间可以起别名
namespace a = b_a_dsdsaddadada
- 命名空间是作用域,不允许用命名空间定义变量
- 命名空间的使用-声明方式
8.1 直接使用命名空间内的变量/类型
using namespace std;
8.2 使用哪个声明哪个
using std::cin;
8.3 使用的时候直接加作用域限制符
std::cin;
构造函数
- 构造的作用
初始化类对象的内存空间 - 构造函数特点
无返回值类型、函数名和类名相同、参数无要求 - 调用时机
有新的类对象产生的时候:为类对象分配内存的时候 - 调用顺序
当其他类的类对象作为当前类的成员变量的时候,构造函数的调用顺序取决于当前类对象的内存分布顺序
class A {
public:
A() { printf("A\n"); }
};
class B {
public:
B() { printf("B\n"); }
};
class C {
public:
C() { printf("C\n"); }
B m_b;
A m_a;
};
int main() {
C obj;
return 0;
}
析构函数
- 作用
释放不属于当前类对象的内存,但是其内存等资源由构造函数申请 - 特点
无返回值类型、无参数、函数名类名前加~ - 调用时机
类对象内存空间被销毁的时候 - 析构的调用顺序
和构造函数相反
构造函数分类
- 默认构造函数
A(){}
- 带参构造函数
参数用来初始化类的成员变量,不同参数的构造函数彼此之间构成重载 - 初始化列表
调用顺序:按照被初始化的成员在对象的内存中的分布顺序
必须在初始化列表里初始化的三个成员:const、引用、其他类的类对象(该类没有无参构造函数) - 拷贝构造函数
和其他构造函数的区别:参数是本类的类对象的引用 - 深拷贝与浅拷贝
浅拷贝:单纯的内存拷贝memcpy
深拷贝:把不属于当前类对象的内存的资源也拷贝一份到目标对象内 - 转换构造函数
特点:一般其参数是基本数据类型
调用时机:当用基本数据类型给类对象赋值或初始化的时候调用的构造函数叫转换构造函数
class D {
public:
D(int a) {
m_nNum = a;
}
int m_nNum;
};
int main() {
D obj(10);
D objA = 20;
objA = 100;
return 0;
}
显式转换、隐式转换
class D {
public:
explicit D(int a) {// 避免隐式转换(通过=的方式)
m_nNum = a;
}
int m_nNum;
};
int main() {
D obj(10);
D objA = 20;
objA = 100;
return 0;
}
- 默认成员函数
Empty(); // 默认构造函数
Empty(const Empty&); // 默认拷贝构造函数
~Empty(); // 默认析构函数
Empty& operator=(const Empty&); // 默认赋值运算符
Empty*operator&(); // 取址运算符
const Empty*operator&()const; // 取址运算符const
静态成员
- 定义
在普通成员(函数)前加static
初始化:静态成员变量只能在类外初始化,不能在构造函数内初始化
属于类,不属于类对象 - 使用
两种方式:类和类对象
类名::静态成员
类对象.静态成员 - 和成员函数的区别
- 静态成员函数和普通成员函数的区别:
this:静态成员函数没有this指针 - 静态成员函数和普通函数的区别
访问权限:静态成员函数可以访问类的所有成员,普通函数只能访问public成员
友元
- 由来
普通函数只能访问public成员,当某些情况下想要访问类的受限成员的时候,就需要友元机制 - 友元函数
- 定义
定义没区别,只不过需要在要访问的类中声明该函数,声明时前面加friend、只要是类内声明都可以,没有权限限制 - 友元函数和普通成员函数的区别:
this:友元函数没有this指针
- 友元类
在要访问的类中声明本类为其友元类
class A {
public:
friend class B;
A() { printf("A\n"); }
};
class B {
public:
B() { printf("B\n"); }
};
- 友元成员函数
- 定义
在要访问的类中,声明本类的某个成员函数为其friend; - 和成员函数的区别
this:友元成员函数有this指针,但是this指针不是友元类的,是该成员函数本身的类的对象的地址
运算符重载
- 单目、双目、特殊符号
- ++/--
后置++/--参数有int,前置没有
class A {
public:
// 前置 ++obj
A& operator++() {
m_nNum++;
return *this;
}
// 后置 obj++
A operator++(int) {
A temp = *this;
m_nNum++;
return temp;
}
int m_nNum;
};
- +/-/=....
- [] << >>
- 重载为成员函数、友元函数
区别:参数不同,重载为成员函数的时候,参数个数是运算符操作数-1
当重载为友元函数的时候,参数个数就是运算符的操作数个数
什么时候重载为成员,什么时候重载为友元:一般单目重载为成员函数,双目重载为友元函数 - 不能重载的运算符
一个点(.)、二个点(?:)、四个点(::)、sizeof - 只能用成员函数形式重载的
() [] -> =
派生与继承
- 继承方式
public/protected/private - 继承后的成员访问权限
- 当原权限和继承权限中:
- 有public的以另一个为最终权限
- 有private的最终权限就是private
- 还剩下一个protected
- 权限重置
基类中的非private的成员,在派生类中的访问权限变更
class A {
public:
int m_nNum;
};
class B :protected A{
public:
A::m_nNum;// 保护变公有
};
- 构造析构函数的调用顺序
先基类->成员对象->自己
多个基类的时候,基类的构造函数调用顺序取决于继承顺序
成员对象,取决于内存分布顺序 - 重定义
返回值类型无要求、函数名相同、参数无要求、作用域不同
基类和派生类会构成重定义 - 多继承的二义性
- 多继承的类彼此之间有同名变量,但是该变量之间没关系
- 多继承的类彼此之间有同名变量,且该变量来自共同基类
- 虚继承可以在某些情况下消除二义性
- 虚继承后类的大小多了4字节:虚基表指针
- 虚继承可以消除来自共同基类的同名变量的二义性,不能消除同名变量没关系的二义性