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