目录
一. C++的诞生和发展
C++诞生于1979年,由贝尔工作室的本贾尼·斯特劳斯特卢普(Bjarne Stroustrup)博士研发,他在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。
C++既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计。 C++几乎可以创建任何类型的程序:游戏、设备驱动程序、HPC、云、桌面、嵌入式和移动应用等。 甚至用于其他编程语言的库和编译器也使用C++编写。
| 发布版本 | 新增内容 |
| C with classes | 类及派生类、公有和私有成员、类的构造析构、友元、内联函数、赋值运算符重载等 |
| C++1.0 | 添加虚函数概念,函数和运算符重载,引用、常量等 |
| C++2.0 | 更加完善支持面向对象,新增保护成员、多重继承、对象的初始化、抽象类、静态成员以及const成员函数 |
| C++3.0 | 进一步完善,引入模板,解决多重继承产生的二义性问题和相应构造和析构的处理 |
| C++98 | C++标准第一个版本,绝大多数编译器都支持,得到了国际标准化组织(ISO)和美国标准化协会认可,以模板方式重写C++标准库,引入了STL(标准模板库) |
| C++03 | C++标准第二个版本,语言特性无大改变,主要∶修订错误、减少多异性 |
| C++05 | C++标准委员会发布了一份计数报告(Technical Report,TR1),正式更名C++0x,即∶计划在本世纪第一个10年的某个时间发布 |
| C++11 | 增加了许多特性,使得C++更像一种新语言,比如∶正则表达式、基于范围for循环、auto关键字、新容器、列表初始化、标准线程库等 |
| C++14 | 对C++11的扩展,主要是修复C++11中漏洞以及改进,比如∶泛型的lambda表达式,auto的返回值类型推导,二进制字面常量等 |
| C++17 | 在C++11上做了一些小幅改进,增加了19个新特性,比如∶static_assert()的文本信息可选,Fold表达式用于可变的模板,if和switch语句中的初始化器等 |
二. 命名空间
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是解决这种问题的
1,命名空间的定义
1)命名空间中可以定义变量/函数/类型
2)命名空间中可以嵌套
3)同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
#include <iostream>
using::std::cout;
using::std::endl;
namespace N1
{
// 1,命名空间中可以定义变量/函数/类型
int rand = 10;
struct Node
{
struct Node* next;
int val;
};
}
// Add.h
namespace N2
{
// 2,命名空间中可以嵌套
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
// Mul.h
namespace N2
{
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
//一个工程中的 Mul.h和上面Add.cpp中两个N2会被合并成一个
int Mul(int left, int right)
{
return left * right;
}
}
int main()
{
cout << "hello world!" << endl;
return 0;
}
2,命名空间的使用
1)加命名空间名称及作用域限定符
符号“::”在C++中叫做作用域限定符,我们通过“命名空间名称::命名空间成员”便可以访问到命名空间中相应的成员。
2)使用using将命名空间中某个成员引入
我们还可以通过“using 命名空间名称::命名空间成员”的方式将命名空间中指定的成员引入。这样一来,在该语句之后的代码中就可以直接使用引入的成员变量了。
3)使用using namespace 将命名空间名称引入
最后一种方式就是通过”using namespace 命名空间名称“将命名空间中的全部成员引入。这样一来,在该语句之后的代码中就可以直接使用该命名空间内的全部成员了。
namespace N
{
int a;
double b;
int Add(int left, int right)
{
return left + right;
}
}
using N::a; //2,使用using将命名空间中某个成员引入
using namespace N;//3,使用using namespace 将命名空间名称引入
int main ()
{
//1,加命名空间名称N及作用域限定符::
N::a = 10;
printf("%d\n", N::a);
//2,使用using将N空间中a引入
b = 11.1;
printf("%lf\n", b);
//3,使用using namespace 将N空间所有成员引入
int c = Add(10, 20);
printf("%d\n", c);
return 0;
}
三. 输入输出
1,定义和特性
1)使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
2) cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,都包含在包含<iostream >头文件中。
3)<<是流插入运算符,>>是流提取运算符。
4)在日常练习中,建议直接using namespace std即可,这样就很方便。
5) using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +using std::cout展开常用的库对象/类型等方式。
2,使用案例
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << endl << c << endl;
return 0;
}
四. 缺省参数
1,定义和特性
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。
在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
可以全缺省也可以半缺省,需要注意的是:
1. 半缺省参数必须从右往左依次来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现
3. 缺省值必须是常量或者全局变量
2,使用案例
#include <iostream>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
// 没传参时,使用缺省参数
// 传参时,使用指定的实参
int main()
{
Func();
Func(10);
return 0;
}
五. 函数重载
1,定义和特性
函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
注意:这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同!
2,使用案例
#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int a, int b)
{
int c = a + b;
cout << c << endl;
return c;
}
double Add(double a, double b)
{
double c = a + b;
cout << c << endl;
return c;
}
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main()
{
Add(100, 200);
Add(1.11, 2.22);
f();
f(10);
f(20, 'a');
f('a', 30);
return 0;
}
六. 引用
1,定义和特性
引用不是新定义一个变量,而是给已存在变量取了一个别名。编译器不会为引用变量开辟内存空
间,它和它引用的变量共用同一块内存空间。
格式为:类型& 引用变量名(对象名) = 引用实体;
引用特性为:
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
#include<iostream>
int main()
{
int d = 22;
int a = 10;
// int& ra; // 该条语句编译时会出错,必须初始化
int& b = a;
int& c = a; // 多个引用
//int& c = d; // 该条语句编译时会出错,不能引用多个实体
printf("%p %p %p\n", &a, &b, &c);
return 0;
}
2,使用场景
1)做参数
C语言的时候经常用交换函数来说明传值和传址的区别。现在我们学习了引用,可以不用指针作为形参了
因为在这里a和b是传入实参的引用,我们将a和b的值交换,就相当于将传入的两个实参交换了。
#include<iostream>
void Swap1(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
void Swap2(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
void Swap3(int* left, int* right)
{
int tmp = *left;
*left = *right;
*right = tmp;
}
int main()
{
int a = 11, b = 22, c = 10, d = 20,e = 1, f = 2;
Swap1(a, b); //传递实参的引用
Swap2(c, d); //实参传给形参,交换失败
Swap3(&e, &f); //传递实参的指针
printf("%d %d\n", a, b);
printf("%d %d\n", c, d);
printf("%d %d\n", e, f);
return 0;
}
2)做返回值
当然引用也能做返回值,但是要特别注意,我们返回的数据不能是函数内部创建的普通局部变量,因为在函数内部定义的普通的局部变量会随着函数调用的结束而被销毁。
注意:
我们返回的数据必须是被static修饰或者是动态开辟的或者是全局变量等等,不会随着函数调用的结束而被销毁的数据。如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
#include<iostream>
using namespace std;
int& Count()
{
static int n = 0;
n++;
return n;
}
int main()
{
int a = Count();
cout << a << endl;
return 0;
}
3,引用和指针的区别
1、引用在定义时必须初始化,指针没有要求
2、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3、没有NULL引用,但有NULL指针
4、在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
5、引用进行自增操作就相当于实体增加1,而指针进行自增操作是指针向后偏移一个类型的大小
6、有多级指针,但是没有多级引用
7、访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理
8、引用比指针使用起来相对更安全
七. 内联函数
1,定义和特性
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
内联系函数的特性:
1) inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
2)inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
3)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到
2,使用案例
inline int Add(int a, int b)
{
return a + b;
}
int main()
{
int ret = Add(1, 2);
return 0;
}
八. auto关键字(C++11)
1,定义和特性
在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它,大家可思考下为什么?
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
2,使用说明
1)用auto声明引用时必须加&,否则创建的只是与实体类型相同的普通变量。
2)用auto在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错
3)auto不能作为函数的参数
4)auto不能直接用来声明数组
#include <iostream>
using namespace std;
int main()
{
int a = 10;
auto b = &a; //自动推导出b的类型为int*
auto* c = &a; //自动推导出c的类型为int*
auto& d = a; //自动推导出d的类型为int
cout << typeid(b).name() << endl;//打印结果为int*
cout << typeid(c).name() << endl;//打印结果为int*
cout << typeid(d).name() << endl;//打印结果为int
auto m = 1, n = 2; //正确写法
auto c = 3, d = 4.0; //编译器报错:“auto”必须始终推导为同一类型
void TestAuto(auto x) {}//编译器报错:auto不能作为函数的参数
int arr1[] = { 1, 2, 3 };
auto b[] = { 4, 5, 6 };//编译器报错:auto不能直接用来声明数组
return 0;
}
九. 基于范围的for循环(C++11)
1,定义和特性
在C++98中,用for循环遍历数组时,需要自定义迭代变量和范围,有时还容易犯错。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
2,使用案例
#include <iostream>
using namespace std;
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
//C++98,for循环遍历数组
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
arr[i] *= 2;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
cout << arr[i] << " ";
}
cout << endl;
//C++,范围for遍历数组
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << " ";
}
cout << endl;
return 0;
}
十. 指针空值“nullptr”(C++11)
1,定义和特性
在C++98中,NULL其实是一个宏,NULL可能被定义为字面常量0,也可能被定义为无类型指针(void*)的常量。字面常量0,既可以是一个整型数字,也可以是无类型的指针(void*)常量,但编译器默认情况下将其看成是一个整型常量,如果要将其按照指针方式来使用,必须对其进行强制转换。
对于C++98中的问题,C++11引入了关键字nullptr:
1、在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的。
2、在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同。
3、为了提高代码的健壮性,在后序表示指针空值时建议最好使用nullptr。
/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else /* __cplusplus */
#define NULL ((void *)0)
#endif /* __cplusplus */
#endif /* NULL */
2,使用案例
#include <iostream>
using namespace std;
void Fun(int p)
{
cout << "Fun(int)" << endl;
}
void Fun(int* p)
{
cout << "Fun(int*)" << endl;
}
int main()
{
Fun(0); //打印结果为 Fun(int)
Fun(NULL); //打印结果为 Fun(int)
Fun(nullptr); //打印结果为 Fun(int*)
Fun((int*)NULL); //打印结果为 Fun(int*)
return 0;
}

795

被折叠的 条评论
为什么被折叠?



