前言
什么是C++
C++(c plus plus)是一种计算机高级程序设计语言,由C语言扩展升级而产生,最早于1979年由本贾尼·斯特劳斯特卢普在AT&T贝尔工作室研发。C语言是结构化和模块化的语言,适合处理较小规模的程序。对于复杂的问题,规模较大的程序,需要高度的抽象和建模时,C语言则不合适。为了解决软件危机, 20世纪80年代, 计算机界提出了OOP(object
oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。Bjarne Stroustrup博士在C语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与C语言的渊源关系,命名为C++。因此:
C++是基于C语言而产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
1.namespace关键字
当使用C语言进行开发时,不同程序员写的不同模块间,难免存在一些列的命名冲突。而在C++中,引入了一个新的关键字即namespace(命名空间)。命名空间可以有效地避免命名冲突和名字污染。
1.1什么是命名冲突
#include<stdio.h>
int rand = 10;
int main()
{
printf("%d\n", rand);
return 0;
}
运行上面的代码,可以发现控制台窗口输出了10。
#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main()
{
printf("%d\n",\n);
return 0;
}
因为全局变量rand和<stdlib.h>中的函数rand产生了命名冲突。那么应该怎么解决这个问题呢?这时候就可以使用命名空间来进行域隔离。
1.2 命名空间域
1.2.1 域的概念
域这个概念其实C语言里就有,那就是全局域与局部域。相同的域内,不能有两个相同名字的标识符。当全局域和局部域命名冲突时,局部域优先。而命名空间域,也就是用namespace关键字创建的域。
1.2.2 域作用限定符
当全局域和局部域有命名冲突时,编译器默认是从局部域例访问相同名字的变量。如果此时想访问全局域的变量,就需要使用域作用限定符(::)。当域作用限定符的左操作数为空时,表示从全局域中访问,当域操作限定符的左操作数为命名空间名,则表示从命名空间域中访问。
#include<stdio.h>
int a = 0;
int main()
{
int a = 1;
printf("%d\n",a);
// ::即域作用限定符
printf("%d\n",::a);
return 0;
}
1.2.3 命名空间域展开
当全局域和局部域以及命名空间域变量名冲突时,编译器默认先访问局部域。当全局域和命名空间域变量命名冲突时,编译器默认访问全局域。若此时局部域和全局域不存在,编译器就不会去访问命名空间域。
如果想访问命名空间域里的变量,可以有三种方法进行访问。第一种是直接展开命名空间域,第二种是指定命名空间域,第三种是展开命名空间域的一部分。
#include<stdio.h>
//int a = 0;
namespace sinzz
{
int a = 2;
}
//展开命名空间域
using namespace sinzz;
int main()
{
//int a = 1;
//指定访问命名空间域
printf("%d\n",sinzz::a);
return 0;
}
补充:当展开了命名空间域后,依旧可以使用指定访问该命名空间域。
//展开一部分举例
namespace sinzz
{
int a = 20;
int b = 30;
int c = 2;
}
//展开命名空间域的一部分
using sinzz::a;
int main()
{
printf("%d\n",a);
printf("%d\n",sinzz::b);
printf("%d\n",sinzz::c);
return 0;
}
补充:在平时练习时,可以直接展开命名空间域。如果是项目的开发,直接展开命名空间域可能会导致和C++库里产生冲突,此时就可以指定访问命名空间域或者展开部分不会冲突的命名空间来避免冲突。
1.3 命名空间域的嵌套
当一个命名空间域设计的足够大时,在这个命名空间域内部,难免存在一系列的命名冲突。命名空间域的嵌套就可以很好的解决这个问题。
namespace sinzzA
{
int a = 2;
int Add(int x, int y)
{
return x + y;
}
namespace sinzzB
{
int a = 6;
int Mul(int x, int y)
{
return x * y;
}
}
}
//对嵌套的命名空间进行访问
int main()
{
printf("%d\n",sinzzA::a);
printf("%d\n", sinzzA::sinzzB::a);
printf("%d\n",sinzzA::sinzzB::Mul(3,3));
return 0;
}
1.4 命名空间域的合并
一个工程中可以有许多相同的命名空间,它们最后会在编译后合并成一个命名空间。
通过上图可以发现,虽然sinzz这个命名空间被定义在了三个不同的文件中,但是最后这三个命名空间还是会合并成一个命名空间的。
2.C++的输入和输出
注意事项:
- 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
- cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含< iostream >头文件中。
- << 是流插入操作符,>>是流提取操作符。
- 使用C++的方式输入和输出更为方便,因为不需要像scanf,printf那样手动控制格式。而是可以自动识别格式进行输入和输出。
2.1 C++的输出
首先,包含iostream头文件,然后,展开std命名空间,使用<<流插入操作符,将想输出的内容插入到cout函数中,然后就可以完成控制台窗口的输出。
#include<iostream>
using namespace std;
int main()
{
//endl--换行符
cout << "hello world" << endl;
return 0;
}
cout这个输出函数相比较于printf函数,还是很智能的。cout可以自动匹配类型进行输出。
int main()
{
int a = 10;
double b = 2.345;
cout << a << " " << b << endl;
return 0;
}
2.2 C++的输入
首先,包含iostream头文件,然后,展开std命名空间,使用>>流提取操作符,将想输入的内容提取到cin函数中,然后就可以完成输入。
int main()
{
int a = 10;
double b = 2.3;
cout << a << " " << b << endl;
cin >> a >> b;
cout << a << " " << b << endl;
return 0;
}
通过上图可以发现,cout函数在输出时会丢失精度。要想解决这个问题的方法有两个,第一个是使用C++的方式来修正,第二个就是使用printf函数来制定格式输出。我个人还是喜欢用第二个方式来解决这一问题。由于C++是兼容C语言的,所以在C++代码中可以混用C语言。
2.3头文件的区别
早期C语言的标准库将其功能实现在全局域中,所以会以.h的格式做出区别。而后来在C++标准库中将功能实现在了std命名域中,为了和C头文件做出区分,也为了正确使用命名空间。后来就规定了C++库不在结尾处以.h格式标识。当然在一些早期的编译器(如vc++6.0)中,是可以使用<iostream.h>这样的方式包含头文件的。但是,后续的编译器均不支持该方式,所以我们需要以< iostream >+命名空间的方式使用C++的输入输出。
3.缺省参数
3.1 缺省参数的概念
缺省参数就是在函数的声明或者定义(定义是一种特殊的声明)时,给定一个缺省值。若调用该函数没有指定实参进行传参,那么参数部分就为给定的缺省值。
#include<iostream>
using namespace std;
void Test(int a = 10)
{
cout << a <<endl;
}
int main()
{
Test();//未指定参数,调用函数使用缺省参数
Test(100);//传参指定参数,不使用缺省参数
return 0;
}
3.2缺省参数的分类
3.2.1 全缺省参数
#include<iostream>
using namespace std;
void test(int a = 10, int b = 20, int c = 30)
{
cout << "a= " << a << endl;
cout << "b= " << b << endl;
cout << "c= " << c << endl;
}
int main()
{
test();
return 0;
}
3.2.2 半缺省参数
#include<iostream>
using namespace std;
void test(int a , int b = 20, int c = 30)
{
cout << "a= " << a << endl;
cout << "b= " << b << endl;
cout << "c= " << c << endl;
}
int main()
{
test(1);
test(1, 2, 3);
return 0;
}
3.3缺省参数使用注意事项
1、缺省参数的定义必须从左向右依次定义,中间不能有间隔。
2、缺省参数不能同时出现在定义和声明当中。如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
个缺省值。
通常在写缺省参数时,都把缺省参数放在函数的声明当中。当然在VS2019编译器下,即使声明和定义的缺省值相同,也还是会报错的。
3、缺省参数的值必须是常量或全局变量。
4.函数重载
4.1 函数重载的概念
函数重载指的是一个函数名可以被多个参数类型不同、参数顺序不同或者参数个数不同的函数共用。C++标准规定在同一作用域下函数重载才是合法的。函数重载通常用于处理实现功能类似但是数据类型不同的函数上。函数重载可以有效地解决工程中的命名污染问题。
#include<iostream>
using namespace std;
int Add(int x, int y)
{
return x + y;
return 0;
}
double Add(double x, double y)
{
return x + y;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(2.3, 3.2) << endl;
return 0;
}
4.2 函数重载的原理
为什么C语言不支持函数重载,而C++支持函数重载呢?C++是如何支持函数重载的呢?带着这两个问题,开始该部分的讲解
首先我们需要明白C/C++程序从代码到程序的过程
4.2.1命名修饰
因为在C语言编译器中,链接过程中,编译器通过函数名在其他的.o文件中查找该函数的地址,而如果函数重名,就不能准确找出那个需要的函数,造成歧义,而在C++编译器中,编译器会对函数名进行名字修饰,即在函数名中加入参数信息,这样在链接过程中,查找函数地址不会造成歧义。
4.2.1.1 linux环境下的观察
vs编译器对命名修饰过于复杂,不方便观察。所以这里用linux环境下的gcc和g++编译器来进行观察。
通过对于gcc编译后产生后目标文件观察可以发现,C语言其实是没有函数进行命名修饰的。下面就将代码采用g++编译。
通过上图便可以清晰地看见了命名修饰就是支持函数重载的基本原理。g++编译器的命名修饰规则为_Z+函数名长度+函数名+参数类型。当然,上图也可以证明只有返回值不同,参数类型,参数数量和参数顺序都相同。这样是不构成函数重载的。因为在调用函数时无法确定它的具体类型。
4.2.1.2 windows环境下的观察
从上图也可以看到,编译器是在链接的时候再回去找func函数的定义,所以这里报的是链接错误,而命名修饰是编译器在编译阶段就处理。
4.3 extern “C”
由于在一个大型的C++项目中,需要多人开发。而一些人擅长C++,当然也有一些人会使用C语言。由于C和C++编译器对函数名字修饰规则的不同,可能就会导致链接失败。。那么就要在函数前加上extern “C”。
//举例使用 extern "C"
#ifdef __cplusplus
extern "C"
{
#endif
int Add(int left, int right);
int Sub(int left, int right);
#ifdef __cplusplus
}
#endif