-
入口 main函数,与文件名无关,多个main会报错,java可以有多个入口
-
完全兼容c
-
C++中常使用cin、cout进行控制台的输入、输出,
cin用的右移运算符>>,cout用的是左移运算符<<
endl是换行的意思
-
函数名相同
-
参数个数不同、参数类型不同、参数顺序不同
-
返回值类型与函数重载无关
-
调用函数时,实参的隐式类型转换可能会产生二义性
-
本质 采用了name mangling或者叫name decoration技术
- C++编译器默认会对符号名(变量名、函数名等)进行改编、修饰,有些地方翻译为“命名倾轧”
- 重载时会生成多个不同的函数名,不同编译器(MSVC、g++)有不同的生成规则
- 通过IDA打开【VS_Release_禁止优化】可以看到
-
被extern "C"修饰的代码会按照C语言的方式去编译
-
如果函数同时有声明和实现,要让函数声明被extern "C"修饰,函数实现可以不修饰
-
由于C、C++编译规则的不同,在C、C++混合开发时,可能会经常出现以下操作,C++在调用C语言API时,需要使用extern "C"修饰C语言的函数声明. 在c++中声明函数,在c文件中写实现,会按c++ name mangling方式找函数实现,然后报错。需要extern "C"
extern "C" { #include "sum.h" }
-
有时也会在编写C语言代码中直接使用extern “C” ,这样就可以直接被C++调用,通过使用**__cplusplus**来区分C、C++环境
C++允许函数设置默认参数,在调用时可以根据情况省略实参。规则如下: 默认参数只能按照右到左的顺序.与swift不同,swift有标签,因为二义性 如果函数同时有声明、实现,默认参数只能放在函数声明中, 默认参数的值可以是常量、全局符号(全局变量、函数名),参数如果是函数,使用的是指向函数的指针
-
使用inline修饰函数的声明或实现,可以使其变成内联函数
-
建议声明和实现都加上inline
-
特点
- 编译器会将函数直接展开为函数体代码
- 减少函数调用开销
- 会增大代码体积
-
尽量不要内联超过10行代码的函数
-
有些函数即使声明为inline也不一定会被编译器内联,如递归函数
-
与宏的区别
- 内联函数和宏都能减少函数调用开销
- 内联函数多了函数特性和语法检测
- 传入a++调用不一样
- 我们经常使用#ifndef、#define、#endif来防止头文件的内容被重复包含
- 使用#pragma once可以防止整个文件的内容被重复包含
- 使用#ifndef、#define、#endif受C\C++标准的支持,不受编译器的任何限制
- 有些编译器不支持#pragma once(较老编译器不支持,如GCC 3.4版本之前),兼容性不够好
- 使用#ifndef、#define、#endif可以针对一个文件中的部分代码,而#pragma once只能针对整个文件
-
在C语言中,使用指针(Pointer)可以间接获取、修改某个变量的值
-
在C++中,使用引用(Reference)可以起到跟指针类似的功能
int age = 20; int &rAge = age; //rAge就是一个引用
-
引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用)
//指针的引用 int a = 10; int b = 20; int *p = &a; int *&rp = p; rp = &b; *p = 30; a:10 b:30 //数组的引用 int array[] = {10,20,30}; int (&rArray)[3] = array;// int &rArray[3]看起来像引用数组,数组中放3个引用不存在 int *a[3] 指针数组,[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素 int (*a)[3]//该语句是定义一个数组指针,指向含3个元素的一维数组。 函数返回值可以赋值 int &func() { int a = 10;//a是全局变量才有价值 return a; } func() = 30 引用交换a和b的值
对引用做计算,就是对引用所指向的变量做计算 在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终” 可以利用引用初始化另一个引用,相当于某个变量的多个别名 不存在【引用的引用、指向引用的指针、引用数组】
-
引用存在的价值之一:比指针更安全、函数返回值可以被赋值
-
引用的本质就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
-
一个引用占用一个指针的大小
-
是常量的意思,被其修饰的变量不可修改
-
如果修饰的是类、结构体(的指针),其成员也不可以更改
int a = 10; const int *p = &a;// *p不可以改 int const *p1 = &a;// *p1不可以改 int * const p2 = &a;// p2不可以改 const int * const p3 = &a; // p3与*p3不可以改 int const * const p4 = &a; // p4与*p4不可以改 const 修饰的是右边的东西,如果右边带*,则*p是常量,不可以改 const Student *pStu = &stu; 下面两句报错,const 修饰*pStu 表明pStu所指向的内存是常量,不能修改 (*pStu).age = 30; pStu->age = 40;
- 引用可以被const修饰,这样就无法通过引用修改数据了,可以称为常引用
- const必须写在&符号的左边,才能算是常引用
int age = 10;
int & const rAge = &age; //const 表明rAge的指向不能改,加不加一个意思
rAge = 20;通过
int const &rAge2 = &age; // rAge不
rAge2 = 30;报错
-
用法
const int &rAge = 50;
const int &rAge = a + b
-
可以指向临时数据(常量、表达式、函数返回值等) 可以指向不同类型的数据 作为函数参数时(此规则也适用于const指针) ✓ 可以接受const和非const实参(非const引用,只能接受非const实参) ✓ 可以跟非const引用构成重载,不是引用加const不会构成重载
int sum(const int &a,const int &b) { retun a + b; }
-
指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
int array[] = {10,20,30}
int (&rArray)[3] = array;
int * const &rArray2 = array;
- c++中有些表达式是可以被赋值的
- 一个变量的地址值,是它所有字节地址中的最小的那个地址值
-
C++中可以使用struct、class来定义一个类
-
struct和class的区别 struct的默认成员权限是public
class的默认成员权限是private实际开发中,用class表示类比较多
- 全局变量:g_
- 成员变量:m_
- 静态变量:s_
- 常量:c_
- 使用驼峰标识
如果类中有多个成员变量,对象的内存又是如何布局的?
在连续地址存放成员变量,还须考虑内存对齐,其中对象地址是首个成员变量的地址
函数与类没有关系,放在代码区,成员函数会传入当前对象的内存地址
通过对象首地址+加该成员变量的偏移来访问成员变量
汇编中 mov dword ptr [eax + 4], 10
偏移4
-
this是指向当前对象的指针
-
对象在调用成员函数的时候,会自动传入当前对象的内存地址
-
可以利用this.m_age来访问成员变量么?
不可以,因为this是指针,必须用this->m_age
- 成员变量私有化,提供公共的getter和setter给外界去访问成员变量
- 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域 不同平台顺序不一样
- 代码段(代码区) 用于存放代码
- 数据段(全局区) 用于存放全局变量等
- 栈空间 每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间, 自动分配和回收
- 堆空间 需要主动去申请和释放,延长对象生命周期,灵活控制
-
在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
-
堆空间的申请\释放
- malloc \ free
- new \ delete
- new [] \ delete [] 使用且new[] 申请数组,必须用delete[] 释放
-
申请堆空间成功后,会返回那一段内存空间的地址 申请和释放必须是1对1的关系,不然可能会存在内存泄露
-
现在的很多高级编程语言不需要开发人员去管理内存(比如Java),屏蔽了很多内存细节,利弊同时存在
利:提高开发效率,避免内存使用不当或泄露
弊:不利于开发人员了解本质,永远停留在API调用和表层语法糖,对性能优化无从下手
memset函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法
构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
-
函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数
-
一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象
-
通过malloc分配的对象不会调用构造函数,创建对象时不要使用malloc
-
错误
默认情况下,编译器会为每一个类生成空的无参的构造函数,查看汇编没有调用构造函数在某些特定的情况下,编译器才会为类生成空的无参的构造函数
-
调用,分清构造函数的调用 与 函数的声明
Person p1(); 是一个函数声明
-
如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化
-
对象初始化
Person() { memset(this,0,sizeof(Person)); }
free(<#void *#>)
传入的是void * 会释放当初申请的连续空间,与传入的是int *,double * 无关
析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
-
函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
-
通过malloc分配的对象free的时候不会调用构造函数
-
构造函数、析构函数要声明为public,才能被外界正常使用
-
对象内部申请的堆空间,由对象内部回收
-
多注意setter和析构的内存管理
class Person {
private:
int m_age;
public:
void setAge(int age);
int getAge();
Person();
~Person();
};
#include 'Person.hpp'
Person::~Person() {
cout<<__func__<<endl;
}
Person::Person() {
memset(this, 0, sizeof(Person));
}
void Person::setAge(int age) {
this->m_age = age;
}
int Person::getAge() {
return this->m_age;
}
命名空间可以用来避免命名冲突,不影响内存布局
namespace HL {
int g_age;
class Person {
};
void test() {
}
}
int main() {
HL::g_age = 10;
HL::test();
return 0;
}
using namespace HL;
或
HL:Person
可以嵌套,合并
namespace HL {
namespace HL2 {
int a;
}
}
HL::HL2::a
namespace HL {
int g_age;
}
namespace HL {
int g_no;
}
等价
namespace HL {
int g_age;
int g_no;
}
有个默认的全局命名空间,我们创建的命名空间默认都嵌套在它里面
C++中没有像Java、Objective-C的基类
-
在内存布局中,父类的成员变量在前,子类的成员变量在后
-
访问权限
-
public/protected/prvate
-
子类内部访问父类成员的权限,是成员本身的访问权限和上一级父类的继承方式中权限最小的那个
-
开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限,
struct Student:Public Person{ };
-
一种便捷的初始化成员变量的方式,只能用在构造函数中
初始化顺序只跟成员变量的声明顺序有关
struct Person {
int m_no; //先初始化
int m_age;
// Person(int no,int age){
// this->m_no = no;
// this->m_age = age;
// }
与上面的初始化方式等价
Person(int no,int age):m_no(no),m_age(age){
}
Person():Person(0,0){ 利用初始化列表传入默认值
}
//Person(int no = 0,int age = 0):m_no(no),m_age(age){
//}
};
struct Person {
int m_no;
int m_age;
Person(int no,int age):m_age(age),m_no(m_age){
//先初始化m_no,m_age没有值 ,
//再初始化m_age 为
}
};
Person p(20,180);
m_no:未知 m_age:180
如果函数声明和实现是分离的
- 初始化列表只能写在函数的实现中
- 默认参数只能写在函数的声明中
构造函数调用构造函数必须放在初始化列表
子类的构造函数默认会调用父类的无参构造函数
◼ 如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数
◼ 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数
struct Person {
int m_age;
Person() :Person(0){
}
Person(int age) :m_age(age){
}
}
Struct Student:Person() {
int m_no;
Student():Student(0,0){
}
Student(int age,int no):Person(age),m_no(no) {
}
}
父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public) 子类指针指向父类对象是不安全的
◼ 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
◼ 多态是面向对象非常重要的一个特性
同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
在运行时,可以识别出真正的对象类型,调用对应子类中的函数
◼ 多态的要素
子类重写父类的成员函数(override)
父类指针指向子类对象
利用父类指针调用重写的成员函数
通过虚函数virtual实现多态
- 只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类中可以省略virtual关键字)
虚函数的实现原理是虚表,这个虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表
如果有虚函数,则对象首地址存放的是虚表的首地址,本质还是指针,
下图为x86的图
所有的Cat对象(不管在全局区、栈、堆)共用同一份虚表
本质汇编分析
会根据虚表的偏移值来调用不同的虚函数
父类与子类有不同的虚表,这样才能保证多态,如果子类没有实现父类的虚函数A,子类虚表直接存储父类该虚函数A的地址
含有虚函数的类,应该将析构函数声明为虚函数(虚析构函数) delete父类指针时,才会调用子类的析构函数,保证析构的完整性
纯虚函数:没有函数体且初始化为0的虚函数,用来定义接口规范
抽象类(Abstract Class)
- 含有纯虚函数的类,不可以实例化(不可以创建对象)
- 抽象类也可以包含非纯虚函数
- 如果父类是抽象类,子类没有完全实现纯虚函数,那么这个子类依然是抽象类
- 可以定义成员变量
class Animal {
virtual void speak() = 0;
}
C++允许一个类可以有多个父类(不建议使用,会增加程序设计复杂度)
多继承下的构造函数
class Undergraduate: public Student, Public Worker {
Undergraduate(int socre,int salary):Student(socre),Worker(salary) {
}
}
多继承下继承多父类的虚函数,会有多个虚表
多继承下的同名函数
调用不同的eat函数,其中eat为非虚函数
Undergraduate ug;
ug:Student::eat();
ug:Worker::eat();
ug:Undergraduate::eat();
ug:eat();
多继承下的同名成员变量
Undergraduate ug;
ug.m_age = 10;
ug.Student::m_age = 20;
ug.Worker::m_age = 30;
菱形继承带来的问题
- 最底下子类从基类继承的成员变量冗余、重复
- 最底下子类无法访问基类的成员,有二义性
虚继承虚基类的成员变量会放在最后面
通过虚继承解决菱形继承问题
静态成员:被static修饰的成员变量\函数
可以通过对象(对象.静态成员)、对象指针(对象指针->静态成员)、类访问(类名::静态成员)
◼ 静态成员变量
- 存储在数据段(全局区,类似于全局变量),整个程序运行过程中只有一份内存
- 对比全局变量,它可以设定访问权限(public、protected、private),达到局部共享的目的
- 必须初始化,必须在类外面初始化,初始化时不能带static,如果类的声明和实现分离(在实现.cpp中初始化)
◼ 静态成员函数
- 内部不能使用this指针(this指针只能用在非静态成员函数内部)
- 不能是虚函数(虚函数只能是非静态成员函数)
- 内部不能访问非静态成员变量\函数,只能访问静态成员变量\函数
- 非静态成员函数内部可以访问静态成员变量\函数
- 构造函数、析构函数不能是静态
- 当声明和实现分离时,实现部分不能带static
const成员:被const修饰的成员变量、非静态成员函数
◼ const成员变量 必须初始化(类内部初始化),可以在声明的时候直接初始化赋值 非static的const成员变量还可以在初始化列表中初始化
◼ const成员函数(非静态) const关键字写在参数列表后面,函数的声明和实现都必须带const 内部不能修改非static成员变量 内部只能调用const成员函数、static成员函数 非const成员函数可以调用const成员函数 const成员函数和非const成员函数构成重载,非const对象(指针)优先调用非const成员函数 const对象(指针)只能调用const成员函数、static成员函数
引用类型成员
在声明的时候直接初始化 通过初始化列表初始化
class Car{
int age;
int &m_price = age;
public:
Car(Int &price):m_price(price){
class Rocket {
int name;
private:
Rocket() { }
static Rocket *ms_rocket;
//禁止拷贝构造和赋值操作
Rocket(const Rocket&)() {};
Rocket &operator=(const Rocket &rocket){};
public:
static Rocket *shared() {
//未考虑的线程安全问题
if (ms_rocket == NULL) {
ms_rocket =new Rocket();
}
return ms_rocket;
}
};
Rocket* Rocket::ms_rocket = NULL; 全局变量声明
Delete 表示,这块内存被回收,可以被别人使用,并不表示这个内存会清零,被抺掉
野指针表示指向被回收的内存
class Car {
int m_price;
char *m_name;
//深拷贝m_name
void copyName(const char *name) {
if (name == NULL) return;
this->m_name = new char[strlen(name) + 1]{};
strcpy(this->m_name,name)
}
public:
Car(int price = 0,const char *name = NULL):m_price(price) {
copyName(name);
}
Car(const Car &car) {
this->m_price = car.m_price;
copyName(car.m_name);
}
~Car() {
if (this->m_name != NULL) {
delete[] this->m_name;
}
}
}
####拷贝构造函数(Copy Constructor)
◼ 拷贝构造函数是构造函数的一种 ◼ 当利用已存在的对象创建一个新对象时(类似于拷贝),就会调用新对象的拷贝构造函数进行初始化 ◼ 拷贝构造函数的格式是固定的,接收一个const引用作为参数 ◼ 通过初始化列表调用父类的拷贝构造函数
Car(const car &car){
}
Car car(100,"bmw")
Car car2 = car;
Car car3(Car2);
Car car4;
car4 = car3;赋值操作,默认浅复制
//car2 ,car3拷贝构造函数,car,car4不是
浅拷贝、深拷贝
◼编译器默认的提供的拷贝是浅拷贝(shallow copy)
将一个对象中所有成员变量的值拷贝到另一个对象
如果某个成员变量是个指针,只会拷贝指针中存储的地址值,并不会拷贝指针指向的内存空间
可能会导致堆空间多次free的问题
◼如果需要实现深拷贝(deep copy),就需要自定义拷贝构造函数
将指针类型的成员变量所指向的内存空间,拷贝到新的内存空间
对象参数和返回值
◼ 使用对象类型作为函数的参数或者返回值,可能会产生一些不必要的中间对象
class Car {
int m_price;
public:
Car(){}
Car(int price):m_price(price){}
Car(const Car &car):m_price(car.m_price) {}
};
void test1(Car car){
}
Car test2() {
Car car(20); // Car(int price)
return car; //外面使用时会调用拷贝构造函数,确实返回的对象在堆空间
}
Car car1(10);//Car(int price)
test1(car1); //Car(const Car &car);
Car car2 = test2();//Car(const Car &car);
Car car3(30);//Car(int price)
car3 = test2();//Car(const Car &car);
匿名对象:没有变量名、没有被指针指向的对象,用完后马上调用析构
使用匿名对象直接调用构造函数,不再调用拷贝构造函数
void test1(Car car){
}
Car test2() {
return Car(20);
}
test1(Car(40));//Car(int price)
Car car3(50);
car3 = test2();//Car(int price)
隐匿构造
C++中存在隐式构造的现象:某些情况下,会隐式调用单参数的构造函数
void test1(Car car){ //传10
}
Car test2() {
return 10;
}
Car car = 10;
可以通过关键字explicit禁止掉隐式构造
explicit Car(int price):m_price(price) {}
◼ C++的编译器在某些特定的情况下,会给类自动生成无参的构造函数,比如
成员变量在声明的同时进行了初始化 有定义虚函数,需要设置虚表 虚继承了其他类 包含了对象类型的成员,且这个成员有构造函数(编译器生成或自定义) 指针类型成员不需要 父类有构造函数(编译器生成或自定义)
对象创建后,需要做一些额外操作时(比如内存操作、函数调用),编译器一般都会为其自动生成无参的构造函数
◼ 友元包括友元函数和友元类
friend Point add(const Point &,const Point &)//友元函数 friend class Math;//友元类
◼ 如果将函数A(非成员函数)声明为类C的友元函数,那么函数A就能直接访问类C对象的所有成员 ◼ 如果将类A声明为类C的友元类,那么类A的所有成员函数都能直接访问类C对象的所有成员 ◼ 友元破坏了面向对象的封装性,但在某些频繁访问成员变量的地方可以提高性能
◼ 如果将类A定义在类C的内部,那么类A就是一个内部类(嵌套类) ◼ 内部类的特点 支持public、protected、private权限 成员函数可以直接访问其外部类对象的所有成员(反过来则不行) 成员函数可以直接不带类名、对象名访问其外部类的static成员 不会影响外部类的内存布局 可以在外部类内部声明,在外部类外面进行定义
声明和实现分离
class Point{
class Math{
void test();
}
};
void Point::Math::test() {
}
-------
class Point {
class Math;
};
class Point::Math{
void test(){}
};
-------
class Point{
class Math;
};
class Point::Math{
void test();
}
void Point::Math::test() {}
◼ 在一个函数内部定义的类,称为局部类 ◼ 局部类的特点 作用域仅限于所在的函数内部 其所有的成员必须定义在类内部,不允许定义static成员变量 成员函数不能直接访问函数的局部变量(static变量除外)
内部类与局部类的权限/作用域与普通类不同
全局函数、成员函数都支持运算符重载
全局函数--1.使用const,const与非const都可以调用这个函数 2.使用&不会调用拷贝构造函数
Point operator+(const Point &p1,const Point &p2){
return Point(p1.m_x + p2.m_x,p1.m_y + p2.m_y);
}
class Point {
成员函数
Point operator+(const Point &point){//const
return Point(this->m_x + point.m_x,this->m_y + point.m_y);
}
//直接返回对象会调用拷贝构造函数,所以加引用,而且加引用后是安全的,因为this仍然存在
Point &operator+=(const Point &point){
this->m_x += point.m_x;
this->m_y += point.m_y;
return *this;
}
//第一个const使用结果不能被赋值,即-p不能被赋值,没有意义,第二个const表明这是一个const函数,const成员可以调用`-(-p)`
const Point operator-() const {
return Point(-this->m_x,-this->m_y);
}
};
注意点:
◼ 有些运算符不可以被重载,比如
1. 对象成员访问运算符:.
2. 域运算符:::
3. 三目运算符:?:
4. sizeof
◼ 有些运算符只能重载为成员函数,比如
1.赋值运算符:=
2.下标运算符:[ ]
3.函数运算符:( )
4.指针访问成员:->
//前++
void operator++() {
this->m_x++;
this->m_y++;
}
//后++
void operator++(int) { //int是语法问题
}
b = ++a + 8;
mov eax,dword ptr [a] //eax = a
inc eax // eax += 1
mov dword ptr [a],eax // a = eax
mov eax,dword ptr [a] // eax = a
add eax,8 // eax += 8
mov dword ptr [b],eax // b = eax
b = a++ + 8;
mov eax,dword ptr [a] // eax = a
add eax,8 // eax += 8
mov dword ptr [b],eax // b = eax
mov eax,dword ptr [a] // eax = a
inc eax // eax += 1
mov dwrod ptr [a],eax // a = eax
++a 会直接让a的值+1,并且返回最新的a进行运算
a++ 会返回a以前的值进行运算,运算完毕之后才会让a的值+1
const Point operator++(int) { //const 返回的值不能改
Point Point(this->m_x,this->m_y);
this->m_x++;
this->m_y++;
return point;
}
全局函数 重载<<函数
ostream &operator<<(ostream &cout, const Point &point) {
return cout<<"("<<point.m_x<<","<<point.m_y<<")";
}
a++
与++a
String str1 = "123";
cout<<str1<<endl; //123
str1 = "222";
cout<<str1<<endl; //\300[\377\256
//造成两个问题
1. str1 = "222";会产生一个临时对象,释放时会调用析构函数,造成str1指向错误的字符串
2. str1 原来指向的“123”无法释放
//解决方式
String &String::operator=(const char * cstring){
if (this->m_cstring) {
delete [] this->m_cstring;
this->m_cstring = NULL; //需要指NULL
}
if (cstring) {
this->m_cstring = new char[strlen(cstring) + 1];
strcpy(this->m_cstring, cstring);
}
return *this;
}
class Sum {
public:
int operator()(int a,int b ){
return a + b;
}
}
◼ 泛型,是一种将类型参数化以达到代码复用的技术,C++中使用模板来实现泛型 ,帮你生成不同的函数
◼ 模板的使用格式如下
template <typename\class T>
typename和class是等价的
◼ 模板没有被使用时,是不会被实例化出来的,不会生成函数
◼ 模板的声明和实现如果分离到.h和.cpp中,会导致链接错误
◼ 一般将模板的声明和实现统一放到一个.hpp文件中
单独编译template .cpp文件时,不会生成函数实现
多参数模板
template <class T1,class T2>
void display(const T1 &v1,const T2 &v2) {
cout << v1 <<endl;
cout << v2 <<endl;
}
display(20,1.7)
template <class Item>
class Array {
//友元 注意<>
friend ostream&operator<<<>(ostream &,const Array<Item> &)
Item *m_data;
}
◼ C语言风格的类型转换符 (type)expression type(expression)
◼ C++中有4个类型转换符
-
Static_cast
- 对比dynamic_cast ,缺乏运行时检测
- 不能交叉转换,不再继承体系不能转换
- 常用于基本数据类型转换,及非const 转成const
- 使用范围比较广,和不写类似
-
const_cast
const Person *p1 = new Person(); Person *p2 = const_cast<Person *>(p1); p2->m_age = 20; Person *p2 = (Person *)p1; //c语言风格
-
dynamic_cast 用于多态类型检测,赋值不安全时,赋值NULL空指针
class Student: Person {}; //继承 Person *p1 = new Student(); Student *stu1 = dynamic_cast<Student*> p1; c语言风格会直接赋值,没有那么安全
-
reinterpret_cast
-
属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅简单的二进制数据拷贝
-
可以交叉转换
-
可以将指针和整数互相转换
int i = 10; double d = i;//不是简单的二进制拷贝,int与浮点类型存储方式不同 int i = 10; double d = reinterpret_cast<double&> i; //要加&语法糖 d的结果不是10, int *p = reinterpret_cast<int *>(100); int i = reinterpret_cast<int>(p)
-
-
atuo
-
可以从初始化表达式中推断出变量的类型,大大简化编程工作
-
属于编译器特性,不影响最终的机器码质量,不影响运行效率
//int i = 10; auto i = 10; auto p = new Person();
-
-
decltype 可以获取变量
int a = 10; decltype(a) b = 20;
-
Nullptr 空指针,可以解决二义性问题
void func(int *p){ cout<<"func(int *) - "<<endl; } func(nullptr);
-
快速遍历
int array[] = { 1,2,3,4}; for (int item:array) { cout << item <<endl; }
-
更简洁的初始化方式
int array[]{11,22,33};
-
lambda表达式
-
有点类似js中的闭包,ios中block,本质就是函数
-
[capture list](params list)mutable exceptionn-> return {function body}
int (*p)(int,int) = [](int a,int b) -> int { return a + b; }; cout<<p(10,20)<<endl; int a = 10; int b = 20; [a,b] { cout << a << endl; cout << b << endl; } //a,b最新的值 [&a,&b] { cout << a <<endl; cout << b <<endl; } [=]{ 默认值捕获,&默认引用捕获 }
-
-
泛型lambda表达式
-
auto func = [](auto v1,auto v2) {return v1 + v2 }; cout << func(10,20.5)<<endl;
-
对捕获变量初始化
int a; auto func = [a = 10]() { cout << a << endl; } func(); //这里a仍然未初始化 cout << a << endl;
可以进行初始化的if,switch语句
if (int a = 10,a > 10) {
a = 1;
} else if(int b = 20;a > 5 && b > 10) {
//...
} //...
switch (int a = 10; a) {
case 1:
break;
case 5:
break;
default:
break;
}
- 语法错误
- 逻辑错误
- 异常
- 其他错误
◼ 异常是一种在程序运行过程中的发生的不好预测的错误(比如内存不够) ◼ 异常没有被处理,会导致程序终止 ◼ throw异常后,会在当前函数中查找匹配的catch,找不到就终止当前函数代码,去上一层函数中查找。如果最终都找不到匹配的catch,整个程序就会终止
try {
//被检测的代码
} catch (异常类型 [变量名]) {
//异常处理代码
} catch (异常类型 [变量名]) {
//异常处理代码
} ...
◼ 为了增强可读性和方便团队协作,如果函数内部可能会抛出异常,建议函数声明一下异常类型
void func1() { //抛出任何可能的异常
}
void func2() throw {//不抛出任何可能的异常
}
void func3() throw(int,double) {//只抛出int,double类型的异常
}
自定义异常类型�
class Exception {
public:
virtual string what() const = 0;
}
class DivideException : Public Exception {
public:
virtual string what() const {
return "不能除以0";
}
}
int divide(int v1,int v2) throw (Exception) {
if (v2 == 0) throw DivideException();
return v1/v2;
}
//使用
try {
int a = 10;
int b = 0;
int c = divide(a,b);
} catch (const Exception &exception) {
cout << exception.what() << endl;
}
拦截所有类型的异常
try {
//被检测的代码
} catch(...) {
//出现异常
}
标准异常std
◼ 传统指针存在的问题 需要手动管理内存 容易发生内存泄露(忘记释放、出现异常等) 释放之后产生野指针
◼ 智能指针就是为了解决传统指针存在的问题
auto_ptr:属于C++98标准,在C++11中已经不推荐使用(有缺陷,比如不能用于数组)
shared_ptr:属于C++11标准
unique_ptr:属于C++11标准
简单实现 1.析构时delete 2.重载运算符
template <class T>
class SmartPointer {
T *m_pointer;
public:
SmartPointer(T *pointer):m_pointer(pointer) { }
~SmartPointer() {
if (m_pointer == nil) return;
delete m_pointer;
}
T *operator()->{
return m_pointer;
}
}
shared_ptr
◼ shared_ptr的设计理念 多个shared_ptr可以指向同一个对象,当最后一个shared_ptr在作用域范围内结束时,对象才会被自动释放 ◼ 针对数组的用法
shared_ptr<Person> ptr1(new Person[5]{},[](Person *p) {delete[] p;});
shared_ptr<Person[]> persons(new Person[5]{});
◼一个shared_ptr会对一个对象产生强引用(strong reference)
◼ 每个对象都有个与之对应的强引用计数,记录着当前对象被多少个shared_ptr强引用着 可以通过shared_ptr的use_count函数获得强引用计数
◼ 当有一个新的shared_ptr指向对象时,对象的强引用计数就会+1
◼ 当有一个shared_ptr销毁时(比如作用域结束),对象的强引用计数就会-1
◼ 当一个对象的强引用计数为0时(没有任何shared_ptr指向对象时),对象就会自动销毁(析构) 注意点
◼ 不要使用裸指针来初始化智能指针,比如以下代码
Person *p = new Person();
{
shared_ptr<Person> p1(p);
}// ~Person()
{
shared_ptr<Person> p2(p);
}// ~Person()
◼可以通过一个已存在的智能指针初始化一个新的智能指针
shared_ptr<Person> p1(new Person());
shared_ptr<Person> p2(p2);
循环引用类似oc 使用weak_ptr解决
class Car {
public:
weak_ptr<Person> m_person;
//weak_ptr 相对于 shared_ptr
}
unique_ptr
◼ unique_ptr也会对一个对象产生强引用,它可以确保同一时间只有1个指针指向对象 ◼ 当unique_ptr销毁时(作用域结束时),其指向的对象也就自动销毁了 ◼ 可以使用std::move函数转移unique_ptr的所有权
unique_ptr<Person> ptr1(new Person());
unique_ptr<Person> ptr2 = std::move(ptr1);
###栈桢
- 保护寄存器的值,push pop
const可以调用static
oc 多态 与swift不同