C++
C++介绍
C++之父 Bjarne Stroustrup
于1979年4月份贝尔实验室的bjarne博士在分析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++
-
头文件
#include <iostream>
#include <stdio.h>
可以继续使用
#include <cstdio>
也可以使用 -
输入,输出
- cout << 输出数据
- cin >> 输入数据
- cin/cout 会自动识别类型
- 注意cin/cout是类对象而scanf/printf是标准库函数
-
增加了名字空间
std::cout
using namespace std;
所有标准类型,对象,函数都位于std命令空间中
名字空间
-
为什么需要名字空间
在项目中,函数名,全局变量,结构,联合,枚举,类,非常有可能名字冲突,而名字空间就对这些命名进行逻辑空间划分(不是物理单元划分),为了解决命名冲突
-
什么是名字空间
在C++中,经常使用多个独立开发的库来完成项目,由于库的开发人员不同,因此命名冲突在所难免,Bjarne为防止命名冲突,给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)
- true/false在C++中是关键字,而在C语言中不是
- true/false在C++中是1字节而在C语言中是4字节
C++的void*
- C语言中void*可以与任意类型的指针自动转换
- C++中,void*不能给其他类型的指针直接赋值,必须强制类型转换,但是其他类型可以自动转换成void*
- C++为什么修改void*
- 为了更安全,所以C++类型检查更严格
- C++可以自动是被类型,对万能指针需求不在那么强烈
操作符别名
某些特殊语言的键盘上没有~,&等符号,所以C++标准委员会为了让C++更具竞争力,为这些符号定义了一些别名,让这些小语种也可以愉快的编写C++代码了
- and &&
- ro ||
- not !
- <% {
- %> }
- %: #
函数重载
-
函数重载
在同一作用域下,函数名相同,参数列表不同的函数,构成重载的关系
-
重载实现的机制
C++代码在编译时,会把函数的参数类型添加到函数名中,借助这个方式来实现函数的重载,也就是说,C++的函数在编译期间经历了换名的过程
因此C++代码不能调用C函数(C语言编译器编译出的函数)
注意 如果两个函数名一样,一定会造成冲突
-
extern "C"{C++中对C函数的声明}
告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件可以与C目标文件合并生成可执行文件)
如果C想调用C++编译出的函数,需要将C++函数定义用
extern "C"{}
-
重载和作用域
函数的重载关系发生在同一作用域下,不同作用域下的同名函数发生的是隐藏
-
重载的解析
当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫做重载解析实参的类型和形参的匹配情况(3种):
- 编译器找到与实参最佳的匹配函数,编译器将生成调用代码
- 编译器找不到匹配函数,编译器将给出错误信息
- 编译器找到好几个能够匹配的函数,但是没有一个是最佳的这种错误叫做二义性
在大多数情况下,编译器都能立即找到一个最佳的调用版本,如果没有,编译器就会进行类型提升,这样被选函数种就可能具有多个可调用的版本,这样就可能产生二义性错误
-
确定重载函数的3个步骤
-
候选函数
函数调用的第一步就是确定可调用的候选函数的集合(函数名,作用域)
-
选择可行函数
从候选函数中选择一个或多个函数(参数个数相同,而且通过类型提升实参可被隐式转换成形参)
-
寻找最佳匹配
优选每个参数都完全匹配的方案,其次参数完全匹配的个数,在其次浪费内存的字节数
-
-
指针类型也会对函数的重载造成影响
C++函数在编译时形参如果类型是指针类型,编译时函数名中会追加Px
默认形参
- 在C++中函数的形参可以设置默认值,调用函数时如果没有提供实参,则使用默认形参
- 如果形参中只有一部分设置了默认形参,则必须靠右排列
- 函数的默认形参是在编译阶段确定的,因此只能使用常量/常量表达式/全局变量作为默认值
- 如果函数的声明和定义需要分开,那么默认形参只设置在声明中
- 默认形参会对函数重载造成影响,设置默认形参时一定要慎重
内联函数
- 普通函数调用时是生成调用指令(跳转指令),然后当代码执行到调用位置时跳转到函数所在的代码段中执行
- 内联函数就会把编译好的二进制指令直接复制到函数的调用位置
- 内联函数的优点就是能够提高函数的运行速度(没有跳转,没有返回),但这样会导致可执行文件增大(冗余),牺牲空间换取时间
- 内联分为显式内联和隐式内联
- 显式内联:在函数前
inline
(C语言C99标准也支持) - 隐式内联:如果结构,类中直接定义的成员函数,则该类型函数自动优化成内联函数
- 显式内联:在函数前
- 宏函数在调用时会把函数体直接展开替换到调用位置,与内联类似
宏函数和内联函数的区别(优缺点)?
宏函数不是真正的函数,只是代码的替换,不会有参数的压栈出栈操作,也不会进行参数的类型检查,因此所有类型都能使用
内联函数是真正的函数,函数调用时会传参,压栈,出栈,返回,并严格检查参数类型,这样就不能通用,想被多种类型调用,需要重载
- 内联适用的条件
- 由于内联会造成可执行文件变大,并且增加内存开销,因此只有频繁调用的简单函数适合作为内联函数
- 调用比较少的,比较复杂的函数不适合内联,内联后并不能显著提高性能,不足以抵消牺牲空间带来的损失
- 带有递归特性和动态绑定特性的函数无法实施内联,因此编译器会忽略声明部分的
inline
关键字
引用
引用就是取艺名
-
引用的基本特性
引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号
- 引用必须初始化,没有空的引用,悬空引用(变量死了,名还留着,函数内返回的局部变量的引用)
- 可以引用无名对象,临时对象,但必须使用常引用
const int & xiu = 18;//int const &
- 引用不能更换目标
- 引用一旦完成了初始化,就和变量一样,它就代表了目标,而不是别名了,一经引用,终身不能引用其他目标
-
引用型参数
- 引用当作函数的参数,达到指针同样的效果,但不具备指针的危险,还比指针方便
- 引用可以非常简单实现函数减共享变量的目的,而且是否使用引用由被调函数说了算
- 引用当作函数参数还能提高传参效率,指针至少还需要4字节,内存而引用只需要增加一条标识符与内存地址之间的映射
-
引用型返回值
- 不要返回局部变量的引用,会造成悬空引用
- 如果返回值是一个临时对象(右值),如果非要用引用来接,使用常引用
const int& =
-
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[]分配的内存,必须通过new[]
返回值前4个字节中存放了数组的长度
-
重复释放
delete/delete[]
不能重复释放同一块内存delete/delete[]
释放野指针的后果不确定,但它可以释放NULL指针
-
内存分配失败
- 当分配的内存过大,没有能需求的整块内存就会抛出异常std::bad_alloc
new/delete
与C语言的malloc相同点不同点(区别)?
不同 身份 运算符 标准库函数 参数 类型(自动计算) 字节数(手动计算) 返回值 带类型的地址 void*地址 调用构造 自动调用 不能调用构造/析构函数 出错 抛出异常 返回NULL 相同 1. 都能管理堆内存 2. 不能重复释放 3. 可以释放NULL 注意 在C++中,尽量使用 引用,new/delete