cin cout
- C++ 中常使用 cin 、 cout 进行控制台的输入、输出
- cin 用的右移运算符 cout 用的是左移运算符
- endl 是换行的意思
int main() {
int age;
cin >> age;
cout << "age is" << age << endl;
}
函数重载
- 规则
- 函数名相同
- 参数个数不同,参数类型不同,参数顺序不同
- 注意
- 返回值类型与函数重载无关
- 调用函数时,实参隐式类型转换可能会产生二义性
- 本质
- 采用了 name mangling 或者叫 name decoration 技术
- C++ 编译器默认会对符号名(变量名、函数名等)进行改编、修饰,有些地方翻译为“命名倾轧”
- 重载时会生成多个不同的函数名,不同编译器( MSVC 、 g++g++)有不同的生成规则
- 通过 IDA 打开 【 VS_Release_ 禁止优化 】 可以看到
extern “C”
- 被
extern "C”
修饰的代码会按照 C 语言的方式去编译
extern "C" {
void func() {
cout << "func()" << endl;
}
void func(int a) {
cout << "func(int a) " << a << endl;
}
}
-
如果函数同时有声明和实现, 要让函数声明被 " extern " 修饰 ,函数实现可以不修饰
extern "C" void func(); extern "C" void func(int a); extern "C" { void func() { } void func(int a) { } }
由于 C 、 C++ 编译规则的不同,在 C 、 C++ 混合开发时,可能会经常出现以下操作, C++ 在调用 C 语言 API 时,需要使用 extern " 修饰 C 语言的函数声明
extern-c.png
-
有时也会在编写 C 语言代码中直接使用 extern “C” ,这样就可以直接被 C++ 调用
extern-c1.png
volidate 关键字
- 不论何时都从内存中读取数据,不使用编译器的优化后的值
默认参数
- C++ 允许函数设置默认参数,在调用时可以根据情况省略实参。规则如下:
- 默认参数只能按照右到左的顺序
- 如果函数同时有声明、实现,默认参数只能放在函数声明中
- 默认参数的值可以是常量、全局符号(全局变量、函数名)
- 函数重载、默认参数可能会产生冲突、二义性(建议优先选择使用默认参数)
内联函数(inline function)
- 使用 inline 修饰函数的声明或者实现,可以使其变成内联函数
- 建议声明和实现都增加 inline 修饰
- 特点
- 编译器会将函数调用直接展开为函数体代码
- 可以减少函数调用的开销
- 会增大代码体积
- 注意
- 尽量不要内联超过 10 行代码的函数
- 有些函数即使声明为 inline ,也不一定会被编译器内联,比如递归函数
内联函数与宏
- 内联函数和宏,都可以减少函数调用的开销
- 对比宏,内联函数多了语法检测和函数特性
- 思考以下代码的区别
#define sum (x) (x +
inline int sum( int x ) { return x + x ;
int a = 10; sum(a++);
引用(Reference)
- 在 C 语言中,使用指针( Pointer )可以间接获取、修改某个变量的值
- 在 C++ 中,使用引用( Reference )可以起到跟指针类似的功能
int age = 20; int &rage = age;
- 注意点
- 引用相当于是变量的别名(基本数据类型、枚举、结构体、类、指针、数组等,都可以有引用)
- 对引用做计算,就是对引用所指向的变量做计算
- 在定义的时候就必须初始化,一旦指向了某个变量,就不可以再改变,“从一而终”
- 可以利用引用初始化另一个引用,相当于某个变量的多个别名
- 不存在 【 引用的引用、指向引用的指针、引用数组 】
- 引用存在的价值之一:比指针更安全、函数返回值可以被赋值
const
- const 是常量的意思,被其修饰的变量不可修改
- 如果修饰的是类、结构体(的指针),其成员也不可以更改
- 以下 5 个指针分别是什么含义?
int age = 10; const int *p0 = &age; int const *p1 = &age; int * const p2 = &age; const int * const p3 = &age; int const * const p4 = &age;
- 上面的指针问题可以用以下结论来解决:
- const 修饰的是其右边的内容
int age = 10; int score = 20; /* *p0 = 20; // 失败, 不能改变指向变量的值 p0 = &score; // 成功,可以改变指针的指向 */ const int *p0 = &age; /* *p1 = 20; // 失败, 不能改变指向变量的值 p1 = &score; // 成功,可以改变指针的指向 */ int const *p1 = &age; /* *p2 = 20; // 成功, 可以改变改变指向变量的值 p2 = &score; // 失败,不能改变指针的指向 */ int * const p2 = &age; /** *p3 = 20; // 失败, 不能改变改变指向变量的值 p3 = &score; // 失败,不能改变指针的指向 */ const int * const p3 = &age; /** *p4 = 20; // 失败, 不能改变改变指向变量的值 p4 = &score; // 失败,不能改变指针的指向 */ int const * const p4 = &age;
image.png
常引用(Const Reference)
-
引用可以被 const 修饰,这样就无法通过引用修改数据了,可以称为常引用
- const 必须写在 符号的左边,才能算是常引用
-
const 引用的特点
- 可以指向临时数据(常量、表达式、函数返回值等)
- 可以指向不同类型的数据
- 作为函数参数时( 此规则也适用于 const 指针
- 可以接受 const 和非 const 实参(非 const 引用,只能接受非 const 实参)
- 可以跟非 const 引用构成重载
当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
数组的引用
- 常见的 2 种写法
int array[] = {10, 20, 30}; int (&ref)[3] = array; int * const &ref2 = array;
引用的本质
- 就是指针,只是编译器削弱了它的功能,所以引用就是弱化了的指针
- 一个引用占用一个指针的大小
类
-
C++ 中可以使用 struct 、 class 来定义一个类
- struct 和 class 的区别
- struct 的默认成员权限是 public
- class 的默认成员权限是 private
// 类的定义 struct Person { // 成员变量 int m_age; // 成员函数 void run() { cout << m_age <<"run()" <<endl; } } class Person { public: int m_age; void run() { cout << m_age <<"run()" <<endl; } } // 访问person对象 Person person; person.m_age = 20; person.run(); // 通过指针访问person对象 Person *p = &person; p->m_age = 30; p->run();
- struct 和 class 的区别
上面代码中 person 对象、 pPerson 指针的内存都是在函数的栈空间,自动分配和回收的, person对象地地址就是成员变量m_age的地址
可以尝试反汇编 struct 和 class ,看看是否有其他区别
实际开发中,用 class 表示类比较多
对象的内存布局
- 成员变量的类型相同时,对象的内存为所有成员变量的内存之和
- 类型不同时,采用内存对齐方式计算
- 空对象占用的内存空间为:1:c++编译器会给每个空对象分配一个字节空间,是为了区分空对象占用内存的位置
- 每个空对象也应该有一个独一无二的内存地址
// 类的定义
struct Person {
// 成员变量
int m_id;
int m_age;
int m_height;
// 成员函数
void display() {
cout << m_id << endl;
cout << m_age << endl;
cout << m_height << endl;
}
}
Person person;
person.m_id = 10;
person.m_age = 20;
person.m_height = 30;
// 创建指针
Person *pPerson = (Person*)&person.m_age; // 指针指向person对象对象的第一个地址
pPerson->m_id = 40; // 相当于是person对象的第一个地址赋值,给m_age赋值
pPerson->m_age = 50;
person.display(); //输出 10 40 50
this
- this 是指向当前对象的指针
- 对象在调用成员函数的时候,会自动传入当前对象的内存地址
- 指向被调用的成员函数/变量所属的对象
- 当形参和成员变量同名时i,可用this指针区分
- 在类的非静态成员函数中返回对象本身,可使用 return *this
- this指针的本质是指针常量,指针的指向不可以修改的
struct Person {
// 成员变量
int m_id;
int m_age;
int m_height;
// 成员函数
void display() {
cout << "m_id is " << this->m_id << endl;
cout << "m_age is " << this->m_age << endl;
cout << "m_height is " << this->m_height << endl;
}
};
class Perosn {
public:
static int m_age;
Perosn& addAge(int age) {
this->m_age = age;
return *this;
}
}
void testPerson() {
Perosn p;
p.addAge(10).addAge(10).addAge(10);
}
封装
- 成员变量私有化,提供公共的 getter 和 setter 给外界去访问成员变量
struct Person {
private:
// 成员变量
int m_id;
public:
int m_age;
int m_height;
// 成员函数
void display() {
cout << "m_id is " << this->m_id << endl;
cout << "m_age is " << this->m_age << endl;
cout << "m_height is " << this->m_height << endl;
}
void setId(int id) {
this->m_id = id;
}
int getId() {
return this->m_id;
}
};
内存空间的布局
memory_zone.png
- 每个应用都有自己独立的内存空间,其内存空间一般都有以下几大区域
- 代码段(代码区)
- 用于存放代码
- 数据段(全局区)
- 用于存放全局变量等
- 静态变量
- 存放常量(符串常量,const修饰的全局变量)
- 代码段(代码区)
- 栈空间
- 存放局部变量,函数参数
- 栈区的数据有编译器管理开辟和释放
- 注意:不要返回局部变量的地址
- 每调用一个函数就会给它分配一段连续的栈空间,等函数调用完毕后会自动回收这段栈空间
- 自动分配和回收
- 堆空间
- 需要主动去申请和释放
- 由程序员分配释放,当程序结束时,由操作系统回收
- 在C++中主要利用new在堆去开辟内存, 利用delete释放
堆空间
- 在程序运行过程,为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
- 堆空间的申请 释放
- malloc \ free
- new \ delete
- new [] delete[]
-
注意
- 申请堆空间成功后,会返回那一段内存空间的地址
- 申请和释放必须是 1 对 1 的关系,不然可能会存在内存泄露
-
现在的很多高级编程语言不需要开发人员去管理内存(比如 Java ),屏蔽了很多内存细节,利弊同时存在
- 利:提高开发效率,避免内存使用不当或泄露
- 弊:不利于开发人员了解本质,永远停留在 API 调用和表层语法糖,对性能优化无从下手
堆空间初始化
int *p0 = (int*)malloc(sizeof(int)); //p1 未初始化
memset(p0, 0, sizeof(int*)); // 将p1的每一个字节都初始化为0
int *p1 = new int; //未初始化
int *p2 = new int(); //被初始化为0
int *p3 = new int(3); // 被初始化为5
int *p4 = new int[3]; // 数组元素未被初始化
int *p5 = new int[3](); // 3个数组元素被初始化为0
int *p6 = new int[3]{}; // 3个数组元素被初始化为0
int &p7 = new int[3]{5}; // 数组首元素被初始化为6,其他元素初始化为0
对象的内存
-
对象的内存可以存在于 3 种地方
- 全局区(数据段):全局变量
- 栈空间:函数里面的局部变量
- 堆空间:动态申请内存( malloc 、 new 等)
// 全局区 Person person; int test() { // 栈空间 Person person; // 堆空间 Person * p = new Person; return 0; }
构造函数(Constructor)
- 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
- 特点
- 函数名与类同名,无返回值( void 都不能写),可以有参数,可以重载,可以有多个构造函数
- 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象
- 注意
- 通过 malloc 分配的对象不会调用构造函数
- 一个广为流传的、很多教程 书籍都推崇的错误结论
- 默认情况下,编译器会为每一个类生成空的无参的构造函数
- 正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
默认情况下,成员变量的初始化
struct Person {
int m_age;
Person() {
/// 将所有成员变量清0
memset(this, 0, sizeof(Person));
cout << "Person()" << endl;
}
Person(int age) {
m_age = age;
cout << "Person(age)" << endl;
}
};
// 全局区:成员变量初始化为0
Person g_person0; // Person()
Person g_person1(); // 不会调动构造方法,仅仅是函数的声明
Person g_person2(10); // Person(int)
void test() {
// 栈空间:没有初始化成员变量
Person person0;// Person()
Person person1(); // 不会调动构造方法,仅仅是函数的声明
Person person2(30); // Person(int)
// 堆空间:没有初始化成员变量
Person * p0 = new Person;// Person()
Person * p1 = new Person();// Person()
Person * p2 = new Person(30); // Person(int)
Person person1; //栈空间(成员变量不会不被初始化)
// 堆空间
Person * p2 = new Person; // 成员变量不会被初始化
Person * p3 = new Person(); // 成员变量初始化为0
Person * p4 = new Person[3]; /// 成员变量不会被初始化
Person * p5 = new Person[3]();//3个person对象的成员变量都初始化为0
Person * p6 = new Person[3]{}; // 3个person对象的成员变量都初始化为0
}
- 如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化
构造函数的分类及调用
- 按参数分类:有参构造和无参构造
- 按类型分为: 普通构造和拷贝构造
三种调用方式
- 括号法
Person p = p(10);
- 显示法
Person p = Person(10);
- 隐式转换法
Person p = 10; // 相当于写了 Person p = Person(10)
构造函数调用规则
- 默认情况下(有需要处理事的时候),c++编译器至少给一个类添加3个函数
- 默认构造函数
- 默认析构函数
- 默认拷贝函数
- 如果用户定义有参构造函数,c++不再提供默认无参构造函数,但是会提供默认的拷贝构造
- 如果用户定义拷贝构造函数, c++不会再提供其他构造函数
- 当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
析构函数(Destructor)
- 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
- 特点
- 函数名以 开头,与类同名,无返回值( void 都不能写),无参,不可以重载,有且只有一个析构函数
- 注意
- 通过 malloc 分配的对象 free 的时候不会调用构造函数
- 构造函数、析构函数要声明为 public ,才能被外界正常使用
obj_mng.png