一、C++介绍
- 发明起因:Bjarne Stroustrup,1979年4月在贝尔实验室分析UNIX操作系统分部内核流量分析时,希望有一种有效的更加模块化的工具
- 发明过程:
- 1979年10月完成了预处理器Cpre,为C增加了类机制,也就是面向对象
- 1983年完成了C++的第一个版本,C with classes(C++)
- C++与C的不同点:
- C++完全兼容C的所有内容
- 支持面向对象编程思想
- 支持运算符、函数重载
- 支持泛型编程、模板
- 支持异常处理
- 类型检查严格
二、第一个C++程序
- 文件扩展名
- .cpp / .cc / .C / .cxx
- 编译器
- g++
- 大多数系统需要额外安装
- ubuntu系统下的安装命令
- sudo apt-get update
- sudo apt-get install g++
- gcc
- 可以继续使用但需要增加参数 -xC++ / -lstdc++
- g++
- 头文件
#include <iostream>
#include <stdio.h>
可以继续使用#include <cstdio>
也可以使用
- 输入/输出
- cout << 输出数据
- cin >> 输入数据
- cout / cin 会自动识别类型
- scanf / printf 可以继续使用
- 注意:cout和cin是标准库类对象,而scanf/printf是标准库函数。
- 增加了自己的名字空间
- std:: cout
using namespace std;
- 所有的标准类型、对象、函数都位于std命令空间中
三、名字空间
- 为什么需要名字空间
- 为了解决命名冲突(函数名、全局变量、结构、联合、枚举、类),需要名字空间对这些命名进行逻辑空间划分(并不是物理单元的划分)
- 在C++中经常使用使用多个独立开发的库来完成项目,由于库的作者或开发人员根本没有见过面,因此命名冲突再所难免,C++之父为防止命名冲突给C++设计一个名字空间的机制
- 什么是名字空间
- 通过使用
namespace XXX
把库汇总的变量、函数、类型、结构等包含在名字空间之中,形成自己的作用域。 namespace XXX {
}// 没有分号
- 注意:
- 名字空间是标识符,在同一作用域下不能重名
- 同名的名字空间自动合并(为了声明和定义分开写)
- 同名的名字空间中如果有重名的依然会命名冲突
- 通过使用
- 名字空间的使用方法
- :: 域限定符
空间名 :: 标识符 // 使用麻烦,但是非常安全
using namespace 空间名
- 把空间中定义的标识符导入到当前代码中,(不建议这样使用)
- 无名名字空间
- 不属于任何名字空间标识符(无名名字空间)
- 无名名字空间中的成员使用 ::标识符 进行访问
- 可以使用无名名字空间访问被屏蔽的全局变量
- 名字空间嵌套
- 名字空间内部可以再定义名字空间
- 内层名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符
- 多层名字空间在使用时逐层分解
n1::n2::n3::num;
namespace n1
{
int num = 1;
namespace n2
{
int num = 2;
namespace n3
{
int num = 3;
}
}
}
- 名字空间的别名
- 由于名字空间可以嵌套,会导致在使用内层成员时过于麻烦,则可以给名字空间取别名来解决。
namespace n123 = n1::n2::n3;
四、C++的结构
- 不再需要
typedef
,定义结构变量时,可以省略struct
关键字 - 成员可以使函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->(C的结构成员可以使函数指针)
- 有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)
- 可以继承,可以设置成员的访问限制(面向对象)
五、C++的联合
- 不再需要
typedef
,定义结构变量时,可以省略union
关键字 - 成员可以使函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->(C的结构成员可以使函数指针)
- 有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)
六、C++的枚举
- 定义、使用方法与C语言基本一致。
- 类型检查比C语言更严格。
七、C++的bool类型
- C++具有真正的布尔类型,不需要额外头文件,(C语言中需要额外加
stdbool.h
) - C++中,true / false 是关键字,C语言中不是关键字
- C++中,true / false 是1字节,C语言中是4字节
八、C++的void*
- C语言中void* 可以与任意类型的指针,自动转换
- C++中void* 不能给其它类型的指针直接赋值,必须强制类型转换,但其它类型的指针可以自动给void* 赋值
- C++为什么这样修改void*?
- 为了更安全,所以C++类型检查更严格。
- C++可以自动识别类型,对万能指针的需求不再那么强烈。
九、操作符别名
- 某些特殊语言的键没有~,&符号,所以C++标准委员会为了让C++更委竞争力,为符号定义了一此别名,让这些小语种也可以愉快编写C++代码。
and = &&
or = ||
not = !
{ = <%
} = %>
# = %
十、C++的字符串
C:\Users\93792\Desktop\指针信息\文档资料\C++中的字符串类.md
十一、函数重载
-
函数重载
- 在同一作用域下,函数名相同,参数列表不同的函数,构成重载关系
-
重载实现的机制
- C++代码在编译时,经历了函数名称换名字的过程
- 如果两个函数名真的一模一样,一定会起冲突
-
extern "C" {}
- 告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了
- C++目标文件可以与C目标文件合并生成可执行程序
- 如果C想调用C++编译出的函数,需要将C++函数的定义用
extern "C"
包括一下。
-
重载作用域
- 函数的重载关系发生在同一作用域,不同作用域下的同名函数构成隐藏关系
-
重载解析
- 当函数调用时,编辑器根据实参的类型和形参的匹配情况,选择一个确定的重载版本
- 实参的类型和形参的匹配情况有三种
- 编译器找到与实参最佳的匹配函数,编译器将生成调用代码
- 编译器找不到匹配的函数,编辑器会给出错误信息
- 编辑器找到多个匹配函数,但没有一个最佳的,这种错误叫做二义性
- 在大多数情况下,编译器都能找到一个最佳的版本,但是如果没有,编译器会进行类型提升,则备选函数中会产生多种版本
-
确定重载函数的三个步骤
- 候选函数
- 确定所有可调用函数的集合(函数名、作用域)
- 选择可行性函数
- 从候选函数选择一个或多个函数(参数个数相同、而且通过类型提升实参可被隐式转换为形参)
- 寻找最佳匹配
- 优选每个参数都完全匹配的方案(参数完全匹配的个数、浪费内存的字节数越少)
- 候选函数
-
const
对函数重载的影响 -
指针类型对函数重载的影响
- C++函数在编译时,形参如果类型是指针,编译时函数名中会追加Px
十二、默认形参
- C++中函数的形参可以设置默认值,调用函数,如果没有提供实参,则使用默认形参
- 如果形参只有一部分设置了默认形参,则必须靠右排列
- 函数的默认形参是在编译阶段确定的,因此只能使用常量、常量表达式、全局变量数据作为默认值
- 如果函数的声明和定义分开,函数的默认形参应该只写在函数的声明
- 默认形参会对函数重载造成影响,设置默认形参时一定要慎重
十三、内联函数
- 普通函数调用时是生成调用指令(跳转),然后当代码执行到调用位置是跳转到函数所在的代码段中执行
- 内联函数是把函数编译好的二进制指令直接复制到函数的调用位置
- 内联函数的优点是提高程序的运行速度(因为没有跳转也不需要返回),但可执行文件增大(冗余)
- 内联分为显式内联和隐式内联(内联函数介绍)
- 显式内联:在函数前,
inline
(C语言C99标准也支持) - 隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数
- 显式内联:在函数前,
- 宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间,所以宏函数与内联的区别(优缺点)?
- 宏函数不是真正的函数,只是代码的替换,不会有参数压栈、出栈以及返回值,不会检查参数类型,因此所有类型都可以使用,但是会产生安全隐患
- 内联函数是真正的函数,被调用时会进行传参,存在压栈、出栈以及返回值,并且严格检查参数类型,缺点是不够通用,只使用一种类型
- 内联函数使用的方法
- 内联会造成可执行文件变大,并增加内存开销
- 适合频繁调用的简单函数
- 不适合调用比较少的复杂函数(调用后不会提高性能,不足以抵消牺牲空间带来的损失)
- 带有递归特性和动态绑定特性的函数,无法实施内联,因此编译器会忽略声明部分的inline关键字
十四、引用
- 引用就是取艺名
- 引用的基本特性
- 引用就是取别名,声明一个标识符为引用,声明一个标识符为引用,就表示该标识符是另一个对象的外号
- 引用必须初始化,不存在空引用,可以使用悬空引用(变量没了,名还在)
- 可以引用无名对象和临时对象,但必须使用常引用
- 引用不能更换目标(引用一旦完成了定义和初始化就和普通变量名一样,它就代表了目标,不能再引用其他目标)
- const 添加笔记
- 引用型参数
- 引用当做函数的参数能达到指针的效果,不仅不具备指针的危险性,还比指针方便
- 引用可以实现函数间的共享变量,是否引用由被调函数决定
- 提高传递参数效率,引用是指增加一条标识符与内存之间的绑定(映射)指针则至少需要4字节内存
- 引用型返回值
- 不允许返回局部变量的引用,会造成悬空引用
- 如果返回值是一个临时对象(右值)如果要使用引用接收的话,必须使用常引用
- 注意
- C++中的引用是一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数)
- 练习
- 练习1:实现一个C++版本的swap函数
十五、C++的内存管理
- new/delete C++具备申请/释放堆内存功能的运算符
- 相当于C语言中的malloc/free
- new 类型;会自动计算类型所需要的字节数,然后从堆分配对应字节数的内存,并返回内存的首地址(具备类型)
- delete 指针; 会自动释放堆内存
- 注意:new/delete 和 malloc/free 不能混用,new/delete会自动调用构造/析构函数
- 数组的分配与释放
- new 类型 [n];n表示数组长度,类、结构会自动调用n次构造函数
- delete [] 指针:通过new[] 分配的内存,必须通过delete[] 释放
- new[] 返回值前4个字节中存放数组的长度
- 重复释放
- delete/delete[] 不能重复释放同一块内存
- delete/delete[] 释放野指针的后果不确定,但释放空指针是安全的
- 内存分配失败
- 当分配的内存过大,没有能达到需求的整块内存时就会抛出异常
- new/delete与C语言的malloc区别
区别 | 身份 | 参数 | 返回值 | 调用构造 | 出错 |
---|---|---|---|---|---|
new/delete | 运算符 | 类型(自动计算) | 带类型的地址 | 自动调用 | 抛异常 |
malloc | 标准库函数 | 字节数(手动计算) | void*地址 | 不能调用构造/析构函数 | 返回NULL |
- new/delete与C语言的malloc相同点
- 都能管理堆内存
- 不能重复释放
- 可以释放NULL
- 注意:在C++中尽量使用引用 、new/delete