C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式等。熟悉C语言之后,对C++学习有一定的帮助,本文章主要目标:
1. 补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的,比如:作用域方面、IO方面、函数方面、指针方面、宏方面等。
2. 为后续类和对象学习打基础。
1. C++关键字(C++98)
看详细介绍点这:C++关键字完整介绍
C语言32个关键字,C++总计63个关键字:
注:C++兼容C语言,C语言能用的关键字和语法C++也能用
2. 命名空间
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
C语言示例如下:
#include <stdio.h> #include <stdlib.h> int rand = 10; // C语言没办法解决类似这样的命名冲突问题,所以C++提出了namespace来解决 int main() { printf("%d\n", rand); return 0; } // 编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
2.1 命名空间定义
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。
1. 正常的命名空间定义
命名空间中可以定义变量/函数/类型
namespace test { // 命名空间中可以定义变量/函数/类型 int rand = 10; int Add(int left, int right) { return left + right; }
- test是命名空间的名字,一般开发中是用项目名字做命名空间名。
- 我在这用的是test,老铁们下去以后自己练习用自己喜欢的命名即可。
2. 命名空间可以嵌套
// test.cpp namespace N1 { 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; } } }
3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
重点:一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
// test.h namespace N1 { int Mul(int left, int right) { return left * right; } }
注:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
2.2 命名空间使用
命名空间中成员该如何使用呢?比如:
namespace test { // 命名空间中可以定义变量/函数/类型 int a = 0; int b = 1; int Add(int left, int right) { return left + right; } struct Node { struct Node* next; int val; }; } int main() { // 编译报错:error C2065: “a”: 未声明的标识符 printf("%d\n", a); return 0; }
命名空间的使用有三种方式:
1.加命名空间名称及作用域限定符:
这种方式是在每次使用命名空间中的成员时都使用完全限定的名称,包括命名空间名和成员名。
int main() { printf("%d\n", test::a); return 0; }
2.使用using将命名空间中某个成员引入:
这种方式是在代码文件的开头使用using声明指示符来引入特定的命名空间中的成员,从而在后续的代码中可以直接使用成员名。
using test::b; int main() { printf("%d\n", test::a); printf("%d\n", b); return 0; }
3.使用using namespace 命名空间名称引入:
这种方式是使用namespace关键字创建一个命名空间的别名,在后续的代码中可以使用别名来引用该命名空间中的成员。
using namespce test; int main() { printf("%d\n", test::a); printf("%d\n", b); Add(10, 20); return 0; }
注:在实际复杂的项目中,最好不要使用第三种方法,因为这很有可能导致各种重定义,重命名。
3. C++输入&输出
新生婴儿会以自己独特的方式向这个崭新的世界打招呼,C++刚出来后,也算是一个新事物
那C++是否也应该向这个美好的世界来声问候呢?我们来看下C++是如何来实现问候的:
#include<iostream> // std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中 using namespace std; int main() { cout<<"Hello world!!!"<<endl; return 0; }
std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
说明:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
- cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
- <<是流插入运算符,>>是流提取运算符。
- 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动控制格式。C++的输入输出可以自动识别变量类型。
5.注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
C++的输入输出可以自动识别变量类型:
#include <iostream> using namespace std; int main() { int a; double b; char c; // 可以自动识别变量的类型 cin>>a; cin>>b>>c; cout<<a<<endl; cout<<b<<" "<<c<<endl; return 0; }
std命名空间的使用惯例:
std是C++标准库的命名空间,如何展开std使用更合理呢?
1.在日常练习中,建议直接using namespace std即可,这样就很方便。
2. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式。
4.缺省参数
4.1 缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
void Func(int a = 0) { cout<<a<<endl; } int main() { Func(); // 没有传参时,使用参数的默认值 Func(10); // 传参时,使用指定的实参 return 0; }
4.2 缺省参数分类
- 全缺省参数:
void Func(int a = 10, int b = 20, int c = 30) { cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; }
- 半缺省参数:
void Func(int a, int b = 10, int c = 20) { cout<<"a = "<<a<<endl; cout<<"b = "<<b<<endl; cout<<"c = "<<c<<endl; }
注意:
- 半缺省参数必须从右往左依次来给出,不能间隔着给。
- 缺省参数不能在函数声明和定义中同时出现,缺省参数的值在函数声明中确定,因此在定义和调用函数时不需要重新指定默认值。
- 调用函数时,可以选择性地提供参数来覆盖默认值。
- 当函数具有多个参数,并且其中某个参数有缺省值时,可以根据需要选择省略某个参数,而只提供后面的参数。
//a.h void Func(int a = 10); // a.cpp void Func(int a = 20) {} // 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
综上所述:
缺省参数提供了更灵活和方便的函数调用方式,可以减少函数调用时需要提供的参数数量,同时保留了对常用参数的默认值设置。
5. 函数重载
自然语言中,一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。
比如:以前有一个笑话,国有两个体育项目大家根本不用看,也不用担心。一个是乒乓球,一个是男足。前者是“谁也赢不了!”,后者是“谁也赢不了!”
5.1 函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同,常用来处理实现功能类似数据类型不同的问题。
1.参数类型不同
int Add(int left, int right) { cout << "int Add(int left, int right)" << endl; return left + right; } double Add(double left, double right) { cout << "double Add(double left, double right)" << endl; return left + right; }
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; }
5.2 C++支持函数重载的原理–名字修饰(name Mangling)
C++语言支持函数重载的原理是通过名字修饰来实现的。在C++中,函数名字可以被修饰,即通过在函数名前加上参数类型列表来生成一种函数名字,这种函数名字被称为 “带参数列表的函数名”。
每种数据类型都会被编译器赋予一个特定的标识符,用于标识该类型,这个标识符被称为“类型的标识符”。当函数名被修饰时,编译器会将函数参数的类型列表和返回值的类型添加到函数名字后面形成一个新的函数名字,这个过程就被称为“名字修饰”。
函数重载的原理:
当程序调用一个函数时,编译器会根据函数名和参数列表组成的函数名字去查找对应的函数。如果有多个函数具有相同的函数名,编译器会根据参数的类型和数量来判断应该调用哪个函数,这就是函数重载的实现原理。
下面来先来看一个使用函数重载时C++名字修饰的例子:
Linux下在g++环境里函数修饰后变成【_Z+函数长度+函数名+类型首字母】
#include <iostream> using namespace std; void func(int x, double y) {} void func(double x, int y) {} int main() { func(1, 1.1); func(1.1, 1); return 0; }
图解: