C++基础语法(命名空间,缺省,重载)

简介: C++基础语法(命名空间,缺省,重载)

前言:C++语法知识繁杂,要考虑的细节很多,要想学好C++一上来就啃书并不是一个很好的方法,书本的内容一般是比较严谨的,但对于初学者来说,很多概念无法理解,上来就可能被当头一棒。因此建议在学习C++之前学好C语言,再听听入门课程,C++有很多的语法概念是对C语言的一种补充,学习过C语言能更好的理解为什么要这样设计,笔者也是初学者,写的这类文章仅是用于笔记总结及对一些概念进行分析探讨,方便以后回忆,因知识有限,错误难以避免,欢迎大佬阅读指教


命名空间

命名空间的定义

阅读下面两段代码,分别用 C++ 和 C语言 打印 “hello C++” 看看其中的区别

#include <iostream>
using namespace std;
int main()
{
  cout << "hello C++" << endl;
  return 0;
}
#include <stdio.h>
int main()
{
  printf("hello C++");
  return 0;
}

我们在看别人写的C++代码时似乎总能看到 using namespace std 这个语句,C++的打印输出函数是 cout ,这可以理解,没有问题,那么为什么要加上using namespace std,这就与命名空间有关了,这个问题,我们等会解决,看下面一段代码

#include <stdio.h>
int num = 20;
int main()
{
  int num = 10;
  printf(" %d ", num);
  printf(" %d ", num);
  return 0;
}

全局变量num 和局部变量num 重名了,按照C/C++编译器的规定,先从局部开始查起,如果局部有该变量的定义,那就用局部的,如果局部找不到该变量的定义,那么就去全局里找,所以两次打印的结果都是10,可我想把全局的 num 和局部的 num 都打印出来呢?C语言是不支持这种形式的,要想把全局的 num 和局部的 num都打印出来,C语言必须有一个变量要改名,而C++可以做到,C++如何做到呢?再看下面一段代码

#include<iostream>
using namespace  std;
namespace  test
{
  int num = 20;
}
int main()
{
  int num = 10;
  cout << num << endl;
  cout << test::num << endl;
  return 0;
}

首先忽视using namespace  std 这行代码,看看namespace test,namespace就是命名空间的关键字,namespace test就是定义一个名为test的空间域,空间域就相当于一堵墙,把 int num = 20 给围了起来,围起来之后,编译器是不会去域空间里查看的,这样就不会与局部里的num造成命名冲突

那我要使用命名空间里的 int num = 20 该怎么办呢?这个时候就要用域作用限定符 “ :: ",即test :: num ,告诉编译器,去 test 这个空间域里找 num,这个时候打印的就是test 里的 num,需要注意的是 int num = 20 虽然在test空间域里,但num仍然是存放到全局静态区里的,test仅仅相当于一堵围墙,不会影响其到内部的定义和声明,可见命名空间能够起到保护的作用,解决了重名变量之间冲突

为了防止我们个人的变量或函数命名与官方库里的命名产生冲突,C++官方把标准库函数放到了 std 这个空间域里,也就是说我们常用的输出函数cout,换行 endl 都是定义到std这个空间域里的,要想使用cout,endl需要域作用符指定后才能使用,如std :: cout,std :: endl


不加std :: ,编译器压根就不认识cout 和 endl



加了 std :: ,就能够正常编译了,问题又来了,每一次使用 cout  和 endl 都要用域限定符 :: 来指定,这样也太麻烦了,C++官方便允许了 using namespace std; 这种写法,什么意思呢?

cout 不是被 std 这堵墙给围住了嘛,using namespace std就相当于把std这堵墙凿个大洞,里面的定义都暴露到全局域中,这个时候编译器再进行编译的时候,就能够在全局区域里找到cout  ,就不需要我们再用 std :: 来指定,当然虽破了个洞,但毕竟还是在里面,因此用 std :: 还是能够找到 cout 的

using namespace std 这样虽然能帮我们偷偷懒,但会导致一个严重的问题,你cout 暴露到全局域里了,假如我在全局域里定义了一个自己的 cout 函数,这不就导致命名冲突了嘛,所以我们自己练习时可以偷懒用,在不明确的环境里慎用

可能你会说,我只是常用cout 而已,并不想把整个std域里的内容都暴露出来,贴心的C++还提供了using std :: cout; 这种写法 ,这意味着只将cout这一个内容暴露到全局域中,std内的其他内容仍处于std的域保护之中

命名空间扩展

上述代码你可能会感到奇怪,int num  = 20; 没有受到域的保护,为什么不是只打印局部里的num呢?反而二者都可以被打印出来,第二个打印函数里多了 :: ,:: 的左边如果是空的话,那就默认为全局域,我们之前说过C/C++编译器的规定,查找变量规则是先从局部开始查起,如果局部有该变量的定义,那就用局部的,如果局部找不到该变量的定义,那么就去全局里找

而这里局部里显然是有num的定义的,但在打印第二个num时加上了 :: ,就是告诉编译器,你去全局域里去找num,如果你不加上域限定符 :: ,那么编译器就不会去全局域里找,两次打印的结果就都是10

看上面这段代码, 我分别在 " C++test.h ","C++test.cpp"这两个文件里同时定义了命名空间test ,发现在测试文件里竟然能够运行,得到正确的结果3,这就是命名空间的另一个规则,

在同一项目工程下,不同文件内的相同域名会被合并成一个空间域,也就是 " C++test.h ","C++test.cpp"这两个文件里的同名空间域 test 被合并成一个空间域


缺省函数

缺省函数的定义

缺省函数就是在定义函数的时候,可以给某个参数赋予一个预定值,如果在调用这个函数时,没有给这个参数传值过去,那么这个缺省参数就会使用这个预定值,什么意思呢?

上图中的程序,我想把x,y的值加上一起,然后乘以Z返回,但我并没有给Z传参数,最终得到结果30

细节是我在给Z定义的时候,给Z赋值为10,意思就是在调用该函数时,可以不用给Z传参,如果给Z传参了,那么Z的值就是你传过去的值,如果不给Z传参,那么Z就会使用你在定义时赋予的预定值,此时的Z就是缺省参数,而函数Add就是缺省函数

定义一个函数时,可以把它的所有参数都设为缺省参数,下图把上面的程序改了改,三个参数都设为了缺省值,在调用时,我都不赋值,得到结果3

缺省参数需要注意的

在定义缺省函数时,需要注意的是缺省参数必须依次从右向左定义

int Add(int x = 1, int y , int Z = 1)  //不可以,没有依次从右向左
int Add(int x , int y  = 1 , int Z = 1) //可以
int Add(int x = 1, int y  = 1, int Z) //不可以,没有依次从右向左
int Add(int x = 1, int y  = 1, int Z = 1) //可以

给缺省函数传参时,必须从左往右依次传参,不能跳过

缺省函数:int Add(int x = 1, int y  = 1, int Z = 1)
调用情况1:Add(1, 2, 3)  //可以
调用情况2:Add(1, 2) //可以
调用情况3:Add(1) //可以
调用情况4:Add(1,  , 3) //不可以,没有依次传参,中间跳过了一个
调用情况5:Add( ,  , 3) //不可以,没有依次传参

在定义缺省函数时,缺省参数不能在函数的声明和定义中同时出现

什么意思呢?看下图

在定义中出现还是在声明中出现没有具体的规定,一般都出现在函数的声明中

函数重载

函数重载的定义

什么是函数重载呢?我们在编写项目程序时,如果引用其他的文件里的函数,可能会出现我们自己定义的函数名与其他文件里的函数名重名了,如果你的函数被调用的次数很多,改函数名很麻烦,而其他文件里的函数擅改函数名可能导致莫名奇妙的问题,这种情况就很让人头疼,而C++给我们提供了函数重载来解决这种问题

C++允许两个或多个函数重名,重名可以,但函数的参数必须不同,参数不同指的是参数的个数,参数的类型,参数的顺序(顺序指的是类型顺序)不同,而这两个或多个参数不同的重名函数就构成了函数重载

上图三个test函数就构成了函数重载, 三个test函数的函数名相同,但是其参数分别是int char double 不同的参数类型,是可以构成函数重载的

能否构成函数重载的情况列举

篇幅原因,这里就不写函数的定义了,只展示声明
int test(int x, int y)           int test(int x,  int y,  int z)
上面两个函数是能够构成函数重载的,因为参数的个数不同
int test(int x, int y)           char test(char x,  char y)
上面两个函数也是能构成函数重载的,因为参数的类型不同
int test(int x, char y)           int test(char x,  int y)
上面两个函数同样是能构成函数重载的,因为参数的类型顺序不同
int test(int x, int y)        int test(int y, int x)
上面两个函数是不能够构成函数重载的,因为这不是类型的顺序不同,而是变量名的顺序不同

函数重载的应用及底层实现

了解过函数重载,大家就能够理解cout函数是如何实现自动识别变量类型的了

cout 本质上是由很多重名的cout函数重载构成的,打印时输入的参数值会传到与它的类型对应的那个cout函数上,从而打印出相应类型的结果

函数重载的底层实现

函数重载是怎么实现的呢?为什么你输入不同类型的值,就能够找到对应的函数呢?在底层是如何区分函数名相同的重载函数的呢?我们一一探讨,这里只是进行简单的探讨,因为这个过程很复杂,展开讲就很麻烦了

编译器会在编译的阶段将出现的各种符号进行解析和汇总,包括变量名,函数名等等,形成一张符号表,便于后续程序调用函数。那想看底层如何区分重载函数的,那就要看看重载的函数在这张符号表上是以什么样的形式记录的,因为不同的编译对符号表中的符号命名规则不同,我们以linux中的g++为参考

假设我们的程序中调用了以下两个函数

int test(int x, int y)         double test(double x, doubley)

那么在编译之后,在符号表中,两个函数的符号名分别为

<_Z4testii>     <_Z4testdd>

可见,两个重载函数的符号名是函数名与参数类型缩写的合并,这也就对应了前面函数重载的规则


可能会有同学有这样的疑问:如果两个函数的函数名相同,参数也全都相同,但是返回值的类型不同,这样能不能构成函数重载呢?答案是不能的,你可能会说如果在符号表中把返回值的类型再加上不就能够识别这是两个不同的函数了嘛

确实,这样编译器能够识别出这是两个函数,问题的关键是编译器不知道该返回哪个函数,你在调用函数的时候只传过去了参数,你没有说要返回哪种类型,这就造成了二义性,所以不能以返回值的类型来设计重载函数





目录
相关文章
|
11天前
|
编译器 C语言 C++
C++入门 | 命名空间、输入输出、缺省参数
C++入门 | 命名空间、输入输出、缺省参数
25 4
|
13天前
|
编译器 C++ 容器
C++语言的基本语法
想掌握一门编程语言,第一步就是需要熟悉基本的环境,然后就是最重要的语法知识。 C++ 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。 类 - 类可以定义为描述对象行为/状态的模板/蓝图。 方法 - 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。 即时变量 - 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。 完整关键字
29 2
|
26天前
|
NoSQL 编译器 Redis
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
c++开发redis module问题之如果Redis加载了多个C++编写的模块,并且它们都重载了operator new,会有什么影响
|
1月前
|
Java 编译器 程序员
C++中的语法知识虚继承和虚基类
**C++中的多继承可能导致命名冲突和数据冗余,尤其在菱形继承中。为解决这一问题,C++引入了虚继承(virtual inheritance),确保派生类只保留虚基类的一份实例,消除二义性。虚继承通过`virtual`关键字指定,允许明确访问特定路径上的成员,如`B::m_a`或`C::m_a`。这样,即使基类在继承链中多次出现,也只有一份成员副本,简化了内存布局并避免冲突。虚继承应在需要时提前在继承声明中指定,影响到从虚基类派生的所有后代类。**
44 7
|
29天前
|
安全 编译器 C++
C++一分钟之-C++中的属性命名空间
【7月更文挑战第22天】C++11引入属性作为元数据,虽无内置属性命名空间,但可通过自定义属性与命名空间组合实现类似效果。例如,创建`perf`命名空间存放`slow`和`fast`属性来标记函数性能。正确使用属性需注意位置、避免重复和确保与实现一致,以提升代码可读性和编译器理解。通过模拟属性命名空间,可以更有效地管理和使用属性。
35 1
|
1月前
|
编译器 C++ 开发者
C++一分钟之-属性(attributes)与属性语法
【7月更文挑战第3天】C++的属性(attributes)自C++11起允许附加编译器指令,如`[[nodiscard]]`和`[[maybe_unused]]`,影响优化和警告。注意属性放置、兼容性和适度使用,以确保代码清晰和可移植。示例展示了如何使用属性来提示编译器处理返回值和未使用变量,以及利用编译器扩展进行自动清理。属性是提升代码质量的工具,但应谨慎使用。
49 13
|
1月前
|
小程序 C++
【C++入门 二 】学习使用C++命名空间及其展开
【C++入门 二 】学习使用C++命名空间及其展开
|
2月前
|
编译器 程序员 C++
C++一分钟之-属性(attributed)与属性语法
【6月更文挑战第28天】C++的属性为代码添加元数据,帮助编译器理解意图。C++11引入属性语法`[[attribute]]`,但支持取决于编译器。常见属性如`nodiscard`提示检查返回值,`maybe_unused`防止未使用警告。问题包括兼容性、过度依赖和误用。使用属性时需谨慎,确保团队共识,适时更新以适应C++新特性。通过示例展示了`nodiscard`和`likely/unlikely`的用法,强调正确使用属性能提升代码质量和性能。
52 13
|
1月前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
1月前
|
C语言 C++ 开发者
C++基础知识(一:命名空间的各种使用方法)
C++在C的基础上引入了更多的元素,例如类,类的私密性要比C中的结构体更加优秀,引用,重载,命名空间,以及STL库,模板编程和更多的函数,在面向对象的编程上更加高效。C语言的优势则是更加底层,编译速度会更快,在编写内核时大多数都是C语言去写。 在C++中,命名空间(Namespace)是一种组织代码的方式,主要用于解决全局变量、函数或类的命名冲突问题。命名空间提供了一种封装机制,允许开发者将相关的类、函数、变量等放在一个逻辑上封闭的区域中,这样相同的名字在不同的命名空间中可以共存,而不会相互干扰。