C++入门篇(一)

简介: C++入门篇(一)

一、C++关键字汇总


在C++98标准下,C++一共有63个关键字,C语言一共有32个关键字。以下是对C++关键字的汇总。



二、命名空间


你有没有过这样的经历,当你在C语言写某个项目的的时候,创建了一个变量,假如是Add,编译器立刻就给你报了一个错,说该变量重定义了,即命名冲突,意思就是你的这个变量已经被定义过了,但是你看了一眼你的代码,发现你之前没有定义过这个变量啊,此时你蒙了,上百度查了一下,发现有人说标准库里面也定义了一个Add,所以你的这个Add变量就是重定义了,你就立马把你的Add改成了其它的名称,就能编译通过了,但是由于这个项目是你们小组共同写的,等到大家都写完了自己的那一部分代码之后就合并成一个项目运行的时候,发现里面有上百个重定义的变量,这时候整个人都崩溃了。要一个一个地修改变量名。


显然,这样的场景使得程序员维护代码的成本变得太高了,发明C++的大佬当然也意识到了这个问题,所以大佬就想:我用一件“外套”封装自己写的那段代码,然后在合并的时候需要用哪件“外套”里面的变量就在变量前指定去哪里找这个变量,这样就能解决命名冲突问题了,这件“外套”就叫做命名空间。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,C++中namespace关键字的出现就是针对这种问题的。


2.1 命名空间的定义


那么命名空间怎么定义呢?


定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中的即为命名空间的成员。




同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间。


注意:一个命名空间定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。


2.2 命名空间的使用


我们如何使用命名空间里面的变量或者函数呢?


有三种方式:


1、可以在使用命名空间中的变量、类型或者函数前写上:


命名空间的名字+作用域限定符。



2、使用using将命名空间中某个成员引入



3、使用using namespace 命名空间名称 引入



三、C++的输入和输出



cin和cout输入输出不用指定类型,编译器会自动判断:


#include <iostream>
using namespace std;
int main()
{
  int a = 0;
  double b = 0;
  //可以自动识别变量的类型
  cout << "输入a,b:    ";
  cin >> a >> b;
  cout << "输出a,b:    ";
  cout << a << " " << b << endl;
  return 0;
}



std命名空间的使用惯例:


std是C++标准库的命名空间,如何展开std使用更合理呢?


  1. 在日常练习中,建议直接using namespace std即可,这样就很方便。


  1. using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模大,就很容易出现。所以建议在项目开发中不要直接展开整个std命名空间,应该像std::cout这样使用时指定命名空间 + using std::cout展开常用的库对象/类型等方式使用std中的内容。


四、缺省参数


什么是缺省参数?


缺省参数是声明或定义函数时为函数的参数指定一个缺省值(即默认值)。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。



缺省参数分类:


缺省参数分为全缺省和半缺省两种。


全缺省参数:



半缺省参数:


半缺省参数是指部分参数缺省,不一定是刚好一般的参数缺省。



需要注意的是:


  1. 半缺省参数必须从右往左依次来给出,不能间隔着给。因为当我们传参的时候默认是从左往右传参的,如果第一个参数x给了缺省值,那么在调用的时候传两个实参过来就会把这两个参数给x和y,而z没有缺省值但是也没有接收到实参,就会出问题,但是从右往左缺省就是z有缺省值,传过来的两个实参给了x和y,这样就能正常地调用函数。


  1. 缺省值必须是常量或者全局变量


  1. C语言不支持(编译器不支持)


  1. 缺省参数不能在函数声明和定义中同时出,原因如下:


//a.h
void Func(int a = 5);
// a.cpp
void Func(int a = 10)
{}
// 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用哪个缺省值。


那么应该是声明的时候给缺省值还是定义的时候给缺省值呢?


这就有必要回顾到编译链接的内容了,我们知道,一般来说,函数的声明是放在.h文件里的,定义放在.cpp文件里,那么在编译的时候是把头文件展开到.cpp文件中,所有在编译的过程中.cpp文件只能看见函数的声明,并不能看见函数的定义的,如果缺省值写在函数的定义处,那么证明在编译的时候.cpp文件中调用这个函数,就必须按照形参的个数传实参,有3个形参就要传3个实参,但是由于这里的形参是有缺省值的,如果是全缺省,那么不需要传实参也是能调用这个函数的,因为声明处没有注明缺省值而导致这个函数调用不了,这显然是不对的。但是如果缺省值写在函数的声明处,那就不存在这个问题了,所以函数的缺省值既不能同时出现在函数的生命和定义处,也只能在声明处给缺省值。


五、函数重载


5.1 函数重载的概念


函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 参数类型 或 参数类型的顺序)不同,常用来处理实现功能类似数据类型不同的问题。


#include <iostream>
using namespace std;
//以下三种情况都能构成函数重载
//1、函数的参数类型不同
int Add(int x, int y)
{
  return x + y;
}
double Add(double x, double y)
{
  return x + y;
}
//2、函数的参数类型顺序不同
double Sub(int x, double y)
{
  return x - y;
}
double Sub(double x, int y)
{
  return x - y;
}
//3、函数的参数的个数不同
void Print(int x, int y)
{
  cout << x << ":" << y << endl;
}
void Print(int x, int y, int z)
{
  cout << x << ":" << y << ":" << z << endl;
}
int main()
{
  cout << Add(1, 2) << endl;
  cout << Add(1.1, 2.2) << endl;
  cout << Sub(1, 2.2) << endl;
  cout << Sub(2.2, 1) << endl;
  Print(1, 2);
  Print(1, 2, 3);
  return 0;
}


注意,返回值的类型不同不能构成函数重载!!!


5.2 C++支持函数重载的原理是什么?


为什么C++支持函数重载,而C语言不支持函数重载呢?


在C/C++中,一个程序运行起来需要经历:预处理,编译,汇编,链接。详情可以看以下这篇博客:https://blog.csdn.net/weixin_70056514/article/details/128958086


  1. 实际项目通常是由多个头文件和多个源文件构成,而通过C语言阶段学习的编译链接,我们可以知道,【当前a.cpp中调用了b.cpp中定义的Add函数时】,编译后链接前,a.o的目标文件中没有Add的函数地址,因为Add是在b.cpp中定义的,所以Add的地址在b.o中。那么怎么办呢?


  1. 链接阶段就是专门处理这种问题,链接器看到a.o调用Add,但是没有Add的地址,就会到b.o的符号表中找Add的地址,然后链接到一起。


  1. 那么链接时,面对Add函数,链接器会使用哪个名字去找呢?这里每个编译器都有自己的函数名修饰规则。


  1. 由于Windows下vs的修饰规则过于复杂,而Linux下g++的修饰规则简单易懂,下面我们使用了g++演示了这个修饰后的名字。



对以上代码用gcc命令编译:




编译后得到以下的汇编代码:



可以看到,C语言的函数名修饰规则就是函数名本身,因为在链接时链接器需要到由.cpp编译生成的.o文件的符号表中寻找函数名对应的地址,再拿着这个地址去找函数的定义,但是从上面的现象可以知道,如果C语言写两个函数名相同的函数,即函数重载,那么编译后生成的汇编代码的函数名是它本身,这就导致在符号表中取函数的地址取找函数的定义的时候具有二义性了,链接器不知道该取哪个函数名的地址,因为它们都是一样的,所以C语言就不能支持函数重载了。


那C++呢?



上面这段代码执行g++指令编译:




编译后生成的汇编代码如下:



通过上面我们可以看出gcc的函数修饰后名字不变。而g++的函数修饰后变成【_Z+函数长度+函数名+类型首字母】。

结论:在linux下,采用g++编译完成后,函数的名字被修饰发生了改变,编译器将函数参数类型信息添加到修改后的名字中。


由此可知C++之所以能支持函数重载,是因为它在编译生成汇编代码时函数名的修饰规则进行了改进,使得函数名相同,参数不同时修饰后的函数名不同,从而取消了链接时可能出现的二义性。


如果两个函数的函数名和参数是一样的,返回值不同是不构成重载的,因为返回值信息并不出现在修饰后的函数名中,所以调用时编译器是没办法区分的。


相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
30 2
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
1月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
68 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
19 1
|
1月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
30 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
24 0

相关实验场景

更多