C++是在C的基础之上,容纳进去了面向对象编程思想,并增加了许多有用的库,以及编程范式 等。熟悉C语言之后,对C++学习有一定的帮助。

本章节主要目标:

  1. 补充C语言语法的不足,以及C++是如何对C语言设计不合理的地方进行优化的。
    比如:作用域方面、IO方面、函数方面、指针方面、宏方面等。
  2. 为后续类和对象学习打基础。

C++初阶课程的核心就是类和对象,然后使用类和对象+模版,实现基础的一些数据结构。

在学类和对象之前,需要先学C++入门,C++的祖师爷觉得C语言很多地方设计得不好,于是开发了许多C++的小语法,去改进C语言的不足。

0. C++关键字(C98)

C++总计 63 个关键字,C语言32个关键字。

ps:下面我们只是粗略看一下C++有哪些关键字,不对关键字进行具体的讲解。后面我们学到以后再细讲。

1. 命名空间

1.1 namespace的价值

在C/C++中,变量函数和后面要学到的都是大量存在的。

这些变量、函数和类的名称若都存在于全局作用域中的话,则很可能会导致很多冲突。

使用命名空间的目的

  • 对标识符的名称进行本地化,以避免命名冲突或名字污染。

 namespace 关键字的出现就是针对这种问题的。

c语言项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入namespace就是为了更好的解决这样的问题。(C的缺陷 / 不足)

#include <stdio.h?
#include <stdlib.h>    //不包含这个头文件,则不会报错
int rand = 10;
int main()
{// 编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”printf("%d\n", rand);return 0;
}//头文件展开,里面的rand()函数和全局的rand变量——>命名冲突

即C语言中,变量不能和函数重名。

命名冲突——C的缺陷之一:存在于编码者与库、(一个大项目的)编码者之间。


C语言是解决不了这个问题的,两个都想叫rand,最终只能有一个在全局域取名为rand。

解决C语言第一个缺陷——命名冲突:同名xx不知道使用哪一个,的第一个C++语法namespace。


7.2 namespace的语法规则

(1)namespace定义

定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{ }即可。——和结构体不一样的是,后面不需要加分号

{}中即为命名空间的成员,可以把日常定义的变量、函数、类型封装到命名空间里面。

  • 命名空间中可以定义变量/函数/类型
    命名空间中是各种标识符的定义:变量名、函数名、类型名……

命名空间中的变量、函数、类型不会和全局冲突,只有指定才会找到。

(2)域

namespace本质是定义出一个域(命名空间域),这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand不再冲突了。

C++中域有函数局部域、全局域、命名空间域、类域

#include <stdio.h>
#include <stdlib.h>
// 1. 正常的命名空间定义
// bit是命名空间的名字,⼀般开发中是用项⽬名字做命名空间名。
// 示例用的是bit,自己练习可以考虑用自己名字缩写,如张三:zs
namespace bit
{// 命名空间中可以定义变量/函数/类型int rand = 10;int Add(int left, int right){return left + right;}struct Node{struct Node* next;int val;};
}int main()
{// 这里默认是访问的是全局的rand函数指针printf("%p\n", rand);// 这里指定bit命名空间中的randprintf("%d\n", bit::rand);return 0;
}

代码编译时,遇到标识符——变量(名)、函数(名)、类型(名),编译器需要去找它的出处(定义),找不到会报错“未声明的标识符”。


编译默认查找顺序——

  1. 当前局部域(自留地)
  2. 全局域找——头文件包含的东西也在全局域(村子野地)

类比做菜摘葱,优先去自留地摘,没有再去村子野地摘。


命名空间相当于在全局域,划分出一些独立的域(命名空间域)
——将一些村子野地,划归成自家的自留地。

编译时不会到其他命名空间中去找(隔壁张大爷自留地)。


这样的话,加了命名空间,同时不包含 <stdlib.h>头文件,就会报错:errorC2065:未声明的标识符。


而指定查找域,则只会去这一个域里面找。

类比做菜摘葱,指定去张大爷地摘。


(3)域的作用

不同域可以定义同名的变量/函数/类型——域可以做到名字的隔离

{ }括起来的都是域,全局域可以不用{ }括起来。

域影响的是编译时语法查找一个变量/函数/类型出处(声明或定义)的逻辑,所以有了域隔离,名字冲突就解决了。

  • 局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期
  • 命名空间域和类域不影响变量生命周期。
    (命名空间域都是修饰全局的,只是把名字给隔离起来了)

域的作用是影响编译时的查找规则——先到局部域,再到全局域&展开的命名空间域

局部优先原则: 默认优先访问局部变量。

C语言,在局部,访问同名全局变量的方法。

C++,在局部,访问同名全局变量的方法——作用域限定符(作用域解析运算符)

(4)嵌套定义

namespace只能定义在全局,当然它还可以嵌套定义。
  变量rand还是全局变量,只是封装到了bit命名空间中。

命名空间内部可以定义变量、函数、类型,还可以定义其他的命名空间。

//2. 命名空间可以嵌套
namespace bit
{// 鹏哥namespace pg{int rand = 1;int Add(int left, int right){return left + right;}}// 杭哥namespace hg{int rand = 2;int Add(int left, int right){return (left + right)*10;}}
}int main()
{printf("%d\n", bit::pg::rand);printf("%d\n", bit::hg::rand);printf("%d\n", bit::pg::Add(1, 2));printf("%d\n", bit::hg::Add(1, 2));return 0;
}

嵌套的命名空间,在使用的时候也要使用多个域作用限定符来访问。

(5)多个同名namespace

项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。

// 多⽂件中可以定义同名namespace,他们会默认合并到⼀起,就像同⼀个namespace⼀样
// Stack.h
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>namespace bit
{typedef int STDataType;typedef struct Stack{STDataType* a;int top;int capacity;}ST;void STInit(ST* ps, int n);void STDestroy(ST* ps);void STPush(ST* ps, STDataType x);void STPop(ST* ps);STDataType STTop(ST* ps);int STSize(ST* ps);bool STEmpty(ST* ps);
}// Stack.cpp
#include"Stack.h"
namespace bit
{void STInit(ST* ps, int n){assert(ps);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;}// 栈顶void STPush(ST* ps, STDataType x){assert(ps);// 满了, 扩容if (ps->top == ps->capacity){printf("扩容\n");int newcapacity = ps->capacity == 0 ? 4 : ps->capacity*2;STDataType* tmp = (STDataType*)realloc(ps->a,newcapacity * sizeof(STDataType));if (tmp == NULL){perror("realloc fail");return;}ps->a = tmp;ps->capacity = newcapacity;}ps->a[ps->top] = x;ps->top++;}//...
}// Queue.h
#pragma once
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>namespace bit
{typedef int QDataType;typedef struct QueueNode{int val;struct QueueNode* next;}QNode;typedef struct Queue{QNode* phead;QNode* ptail;int size;}Queue;void QueueInit(Queue* pq);void QueueDestroy(Queue* pq);// 入队列void QueuePush(Queue* pq, QDataType x);// 出队列void QueuePop(Queue* pq);QDataType QueueFront(Queue* pq);QDataType QueueBack(Queue* pq);bool QueueEmpty(Queue* pq);int QueueSize(Queue* pq);
}// Queue.cpp
#include"Queue.h"
namespace bit
{void QueueInit(Queue* pq){assert(pq);pq->phead = NULL;pq->ptail = NULL;pq->size = 0;}// ...
}// test.cpp
#include"Queue.h"
#include"Stack.h"// 全局定义了一份单独的Stack
typedef struct Stack
{int a[10];int top;
}ST;void STInit(ST* ps){}
void STPush(ST* ps, int x){}int main()
{// 调用全局的ST st1;STInit(&st1);STPush(&st1, 1);STPush(&st1, 2);printf("%d\n", sizeof(st1));// 调用bit namespace的bit::ST st2;printf("%d\n", sizeof(st2));bit::STInit(&st2);bit::STPush(&st2, 1);bit::STPush(&st2, 2);return 0;
}

情景1:假设上述代码中,栈的队列的初始化函数,都希望叫作Init(不希望用名字来区分),那么就需要将栈和队列的代码,放入不同的命名空间中。

情景2:害怕自己的栈的STInit()和别人的冲突,就不能直接#include "Stack.h",这样会直接暴露在全局域,需要先在头文件中,把代码都放到命名空间中,才能#include "Stack.h"

一个一般的项目有几十上百个头文件,定义多少个命名空间?
(不分命名空间的话因为变量、函数、类型……的名字就得拉扯很久理不清)

一般分组合作,一个组内几个头文件共用一个命名空间。

不同文件可以定义同名的命名空间,同名的命名空间可合并

合并后(同一个域)有同名会报错——命名冲突——1改名,2嵌套

而同一个文件也没必要搞多个命名空间,一个足矣,即都放到一起——因为多个文件都可以共用一个命名空间。

(6)标准库——标准命名空间

C++标准库都放在一个叫 std (standard的缩写)的命名空间中。

7.3 命名空间使用(3种方法)

编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错。

#include<stdio.h>
namespace bit
{int a = 0;int b = 1;
}int main()
{// 编译报错:error C2065: “a”: 未声明的标识符printf("%d\n", a);return 0;
}

其他的例证:

原因:有两个rand,这里printf里的rand默认取库里面的rand,即函数rand(),代表一个函数指针,d%改成p%就可以打印出这个函数指针。


所以我们要使用命名空间中定义的变量/函数,有以下三种方式:

指定命名空间访问,项目中推荐这种方式。

using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。

展开命名空间中全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。
(只有一个命名空间,或只有少量命名空间且相互之间不冲突时,可以完全展开)

总结。

  • 指定域。
  • 展开命名空间。(不指定域也不会报错,会到展开的命名空间中去查找)
    • 展开某个成员。
    • 完全展开。
#include<stdio.h>
#include<stdlib.h>namespace bit
{int rand = 0;int x = 0int y = 0
}
// 指定命名空间访问——最安全
int main()
{printf("%d\n", rand);        //默认全局——库函数rand()printf("%d\n", bit::rand);return 0;
}// using将命名空间中某个成员展开——某个频繁使用的成员
using bit::x;
int main()
{printf("%d\n", bit::y);printf("%d\n", x);printf("%d\n", x);printf("%d\n", x);printf("%d\n", x);printf("%d\n", x);        //x经常使用,y偶尔使用return 0;
}// 展开命名空间中全部成员——最危险
using namespce bit;
int main()
{printf("%d\n", rand);printf("%d\n", x);printf("%d\n", y);return 0;
}

展开命名空间后不指定域也不会报错的前提——和其他展开的命名空间域 or 全局域不冲突。

故虽然麻烦一点,尽量还是别展开。


展开头文件:把头文件的内容在预处理阶段拷贝过来。

展开命名空间:命名空间是一个域,域的展开是开放访问权限,域的作用是影响编译时查找——先到局部域,没有再到全局域,最后如果有展开的命名空间,就会到展开的命名空间内查找

编译默认查找
a、当前局部域                        : 自留地
b、全局域找                            : 村子野地
b、到展开的命名空间中查找   : 相当于张大爷在自己的自留地加了声明,谁需要就来摘

没加这个声明时,默认不会到命名空间中去找。

  • 注意展开不是放到全局域,展开后仍然是两个域,只是展开的命名空间域、全局域在查找标识符的出处的时候,具有等价的优先级——局部域之后。

展开的命名空间域、全局域有同名函数不会报错——不调用就不会报错。

一旦调用就会产生调用歧义——调用哪个都可以,全局域、命名空间域都能去找。
(不指定的情况下,全局域、展开的命名空间域都会同等优先级地搜索)

但是指定域的方式去调用也不会出错

::func();        //指定全局域里面去找//或者bit::func()      //指定命名空间域里面去找

有了命名空间,就能很好地解决命名冲突的问题。

类型的“域限定符”放在类型名前面——Node前面。(标识符前面)

编译器的一个很智能的点:

应用:


总结——命名空间的价值

命名空间就是把某块空间圈起来, 圈起来之后影响了查找规则,以此解决了命名冲突。
(冲突的本质:不知道用哪一个)


2. C++输入&输出

c++搞了一套新的输入输出流——IO流  /  iostream。

新的头文件“iostream”    ——    相当于是stdio.h的进化版。

也可以继续包含stdio头文件使用 printf/scanf,但是c++更喜欢使用cout/cin——因为其在命名空间std内,产生隔离更安全。

这里的cout中的c不是c++的c,而是console(控制台)的 c。

windows下的控制台,相当于linux下的终端。


<iostream> 是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输

出对象。


为什么头文件iosream没有.h——特别老的c++标准带.h(还没有命名空间),如老编译器vc6.0就可以用。

后来出了命名空间,就包到新的不带.h的头文件里了。

头文件.h只是一个标识,现在c++标准库的头文件几乎都不带.h。

C++对C兼容的时候,对C的头文件都封装了一个不带.h的版本。

不带.h的版本,就是用命名空间封装过中的版本。(<stdlib.h>    ==    <cstdlib>)

注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应
头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,
规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因
推荐使用<iostream>+std的方式。

std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输

入流。

std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流。

std::endl 是一个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区。

 << 是流插入运算符, >> 是流提取运算符。
(C语言还用这两个运算符做位运算左移/右移)

cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出。
他们都包含在包含
< iostream >头文件中。

使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,不需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的,这个以后会讲到).

  • 其实最重要的是 C++的流能更好的支持自定义类型对象的输入输出

IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识一下C++ IO流的用法,后面我们会有专门的一个章节来细节IO流库。

cout/cin/endl 等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中。

编译器查找的时候,默认先去局部,再去全局,全局域包含了头文件,头文件展开后按理来说应该有了,但是C++的标准库里面做了一件事——为了防止标准库里面的东西和程序员自己定义的东西冲突了,所以标准库里面的代码被封装进了一个命名空间——std。

直接使用cout,并不会到命名空间std中去查找。

所以要通过命名空间的使用方式去用他们——3种方式

① 指定命名空间域

② 完全展开

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
int main()
{int a = 0;double b = 0.1;char c = 'x';cout << a << " " << b << " " << c << endl;std::cout << a << " " << b << " " << c << std::endl;scanf("%d%lf", &a, &b);printf("%d %lf\n", a, b);// 可以自动识别变量的类型cin >> a;cin >> b >> c;                    //可以连续提取——不需要取地址cout << a << endl;cout << b << " " << c << endl;    //可以连续插入cout << b << " " << c << '\n';    //换行有两种方式://'\n'//endlreturn 0;
}

③ 指定展开

cin

  • 作用:从console里面把输入的数据拿出来放到这个对象(C习惯叫变量,c++习惯叫对象)里面。
  • 优点:不用指定格式;也不需要取地址。
  • cin不常用就不用指定展开。

一般日常练习中我们可以using namespace std,实际项目开发中不建议using namespace std。

这里我们没有包含<stdio.h>,也可以使用printf和scanf,在包含<iostream>间接包含了。vs系列 编译器是这样的,其他编译器可能会报错。

cout、cin优点

  • 可以“自动识别类型”(printf需要指定类型:%d、%s......),即可以随便插入,不用考虑占位符
  • 并且可以连续地输出cout<<i<<j;
  • 并且可以在中间自由插入。
    • cout<<i<<"abcd"<<j;(插入字符串)
    • cout<<i<<" "<<j;(插入空格隔开)

printf、scanf优点

  • 更高效(99%的场景都不需要考虑这个)——因为C++要兼容C语言,会有一定的效率的影响
#include<iostream>
using namespace std;
int main()
{// 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码// 可以提⾼C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}

cin、cout也有相关的精度控制函数,默认是有多少输出多少。

c++.ostream


cout需要控制精度、宽度时非常麻烦,涉及一系列函数——>能兼容printf就用printf。

但是VS下的iostream是包含了printf、scanf,Linux下就不一定了,可能会报错,就需要多包一个头文件。


竞赛的2个tips

① C++IO效率

#include<iostream>
using namespace std;
int main()
{// 在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码// 可以提⾼C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}

② 万能头文件

万能头文件<bits/stdc++.h>:把c++常见的基本都包进来了——但是VS不支持。

但是一展开就会导致程序变大很多,日常、项目都不建议使用,竞赛可用。

展开头文件有极大的销耗。

3. 缺省参数

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

(有些地方把缺省参数也叫默认参数)

缺省参数分为:全缺省参数、半缺省参数。

全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。

C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。

(从左往右缺省带有歧义)

从右往左给缺省值,函数调用就不存在歧义——调用函数的实参按形参列表从左往右依次给到形参

带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。

函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定只能由函数声明给缺省值。——如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。

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

C语言不支持(编译器不支持)。

#include <iostream>
#include <assert.h>
using namespace std;void Func(int a = 0)
{    cout << a << endl;
}int main()
{Func();     // 没有传参时,使用参数的默认值Func(10);   // 传参时,使用指定的实参return 0;
}

缺省参数的优势:

可以不传参数,函数调用按默认值运行——可传,可不传,提高了程序的灵活度。

#include <iostream>
using namespace std;// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{cout << "a = " << a << endl;    cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}// 半缺省——从右往左缺省 && 不能间隔着给
void Func2(int a, int b = 10, int c = 20)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl << endl;
}int main()
{//带缺省参数的函数,函数调用就有多种方式了Func1();Func1(1);Func1(1,2);      //规定是按顺序传,传一个参数就是给a,传两个参数就是给a,b——所以不允许间隔着给缺省值 //Func1(1, ,3)   //不能跳跃着传Func1(1,2,3);     Func2(100);Func2(100, 200);Func2(100, 200, 300);return 0;
}

缺省参数的用途——C的栈的初始化(开0个空间)是实现得不太好的。

在首次插入时开4个空间,后续扩容2倍。

// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;typedef int STDataType;
typedef struct Stack
{STDataType* a;int top;int capacity;
}ST;
void STInit(ST* ps, int n = 4);// Stack.cpp
#include"Stack.h"
// 缺省参数不能声明和定义同时给
void STInit(ST* ps, int n)
{assert(ps && n > 0);ps->a = (STDataType*)malloc(n * sizeof(STDataType));ps->top = 0;ps->capacity = n;
}// test.cpp
#include"Stack.h"
int main()
{ST s1;STInit(&s1);// 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容ST s2;STInit(&s2, 1000);return 0;
}

定义一个栈——>对栈初始化——>插入1000个数据 这个时候会导致一个问题——>这个程序会有大量的扩容——>而且扩容到后面,异地扩容消耗很大(如果没有足够的空间:开新的空间——拷贝数据——释放旧的空间),而且越往后消耗越大(拷贝空间、拷贝时间)

C语言传统的解决方案:

(1)定义一个宏N,一开始就先开一堆空间。

这样写死的方式都是不太好的,因为当初始数据较小时就会造成比较大的空间浪费。

(2)增加一个参数n,灵活一点,能够帮助我们去控制,即要初始化多少你直接给我
(最好不要使用宏——>不好用)

(3)在C++,如果一开始不知道要初始化多大的空间,就可以给参数一个官方指导值(缺省值)

有了缺省参数,就能在初始化时不去指定具体开辟多大的空间,不指定就默认申请4字节的空间。

——体现半缺省的价值:知道要开多大就传多大,不知道就开默认值。

4. 函数重载

4.1 函数重载的概念

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

函数重载的要点

  • 同一作用域:同名函数——>形参不同(个数不同、类型不同)。

这样C++函数调用就表现出了多态行为,使用更灵活。


C语言是不支持同一作用域中出现同名函数的。

error C2084:“函数“int Add(int,int)”已有主体

#include<iostream>
using namespace std;// 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;
}int main()
{Add(10, 20);Add(10.1, 20.2);f();f(10);f(10, 'a');f('a', 10);return 0;
}

c++可根据形参匹配同名函数
(同一作用域内的同名函数——都在同一命名空间、或都在全局)
(不同域本就允许同名函数)

之前是在不同作用域,即使有“不去访问未展开的命名空间”的规定,也可以根据参数去调用命名空间内的函数。

原理都是参数匹配。


4.1.1 不构成函数重载的情况

(1)返回值不同
void fxx()
{//……
}int fxx()
{return 0;
}

返回值不同是不构成重载的,函数重载根本就不看返回值,有返回值没有返回值都没关系,只看参数列表。

因为看返回值的不同,无法区分函数调用,调用时会根据参数去匹配函数。

换一个角度看,返回值在调用的时候并不是必须的,返回值可以不接收,但是参数必须传递。

上述代码结果:报错。

(2)缺省值不同
void f(int a = 10)
{//……
}void f(int a = 20)
{//……
}

上述代码结果:报错。

(3)不同的(命名空间)域
//情景1:两个同名函数——不构成重载,但是可以同时存在
namespace bit1
{void func(int a){//……}
}namespace bit2
{void func(int b){//……}
}

情景2:若是都叫bit1——不构成重载,不能同时存在。

还是在同一命名空间(会合并),那同一作用域函数要同名存在,必须满足重载规则。


4.1.2 函数重载的调用歧义

// 下⾯两个函数构成重载
// f()但是调用时,会报错,存在歧义,编译器不知道调用谁
void f1()
{cout << "f()" << endl;
}void f1(int a = 10)
{cout << "f(int a)" << endl;
}int main()
{f1();return 0;
}

不调用就不会报错

全缺省和无参的同名函数,构成函数重载,但是调用f()会存在歧义

调用全缺省的函数时给参数,也不会发生调用歧义。

4.1.3 非函数重载的调用歧义

调用歧义:两个swag(int*,int*)都可以调——swag(&a,&b)不知道调哪一个 。    

但是swag(&c,&d)知道——只有全局域有其定义swag(double*,double*)

展开命名空间,两个swap还是在各自的域中,不构成函数重载。
(不构成函数重载但是都可以存在,不调用就不会出错,这里的错误在于调用歧义)

命名空间都展开——不构成重载关系——还是在各自的作用域,同一个域内才有重载的概念。

重载:同一个菜地。
展开:菜地旁插了块牌子。

4.1.4 隐式类型转换

//只有一个函数的时候——才存在隐式类型转换
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()
{int a = 0, b = 1;double c = 0.1, d = 1.1;f(1, 'a');f('a', 1);                //只有一个f(int,char),调用f(char,int)会有转换return 0;
}//存在多个函数同名:只有匹配的问题,没有转换的问题——不让转
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()
{int a = 0, b = 1;double c = 0.1, d = 1.1;f(1, 'a');f('a', 1);//两个同名函数,这个调用就存在歧义f('a', 'a')//这个调用是带有条件的,条件就是隐式类型转换//调用第一个f(int,char)就是一参char——>int//调用第二个f(char,int)就是二参char——>int//这里的调用就存在歧义,不知道调哪一个——调用必须得没有歧义return 0;
}

4.2 函数重载的原理

C++支持函数重载的原理--名字修饰(name Mangling)

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

——函数名修饰规则

注意点:只要函数调用去匹配函数声明的时候,在参数上是匹配的,那么这个调用就是合法的,即函数调用的合法性与函数定义无关,只与函数声明有关。

声明和定义分离时,就会有缺地址的问题——地址在外部符号表。

只有声明,没有定义(没有地址),call指令就只检查:

使用符不符合规则—参数匹不匹配—语法正不正确

语法不正确:报出语法错误。

语法正确:在链接的时候拿函数名去符号表里面找地址。

直接拿函数名去找吗???

前面讲这么多就是为了说明:

  • 在链接时有这么一个查找的过程,为什么C不支持,而c++支持?
  • 因为在链接的时候要用函数名去找地址,如果声明和定义分离,即包.h一起编译的汇编代码缺地址,而调用一个函数,要用函数名去找,C语言直接用函数名去找,就区分不开。(声明和定义没有分离,是不找的)
  • 而c++用修饰后的函数名去找,不同的编译器有具体的函数名修饰规则(把参数带进来),但都会把函数的参数的类型带进来
  • 大概就是公共前缀+函数名+形参类型

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

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

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

……

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

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

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

采用C语言编译器编译后结果

结论:在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。

采用C++编译器编译后结果

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

Windows下名字修饰规则

对比Linux会发现,windows下vs编译器对函数名字修饰规则相对复杂难懂,但道理都是类似的,我们就不做细致的研究了。

【扩展学习:C/C++函数调用约定和名字修饰规则--有兴趣好奇的同学可以看看,里面有对vs下函数名修饰规则讲解】

C/C++的调用约定

6. 通过这里就理解了C语言没办法支持重载,因为同名函数没办法区分。而C++是通过函数修

饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载

7. 如果两个函数函数名和参数是一样的,返回值不同是不构成重载的,因为调用时编译器没办

法区分。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/94081.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/94081.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/94081.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ANSI终端色彩控制知识散播(II):封装的层次(Python)——不同的逻辑“一样”的预期

基础高阶各有色&#xff0c;本原纯真动乾坤。 笔记模板由python脚本于2025-08-22 18:05:28创建&#xff0c;本篇笔记适合喜欢终端色彩ansi编码和python的coder翻阅。 学习的细节是欢悦的历程 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简单复述…

前端无感刷新 Token 的 Axios 封装方案

在现代前端应用中&#xff0c;基于 Token 的身份验证已成为主流方案。然而&#xff0c;Token 过期问题常常困扰开发者 —— 如何在不打断用户操作的情况下自动刷新 Token&#xff0c;实现 "无感刷新" 体验&#xff1f;本文将详细介绍基于 Axios 的解决方案。什么是无…

【数据结构】线性表——链表

这里写自定义目录标题线性表链表&#xff08;链式存储&#xff09;单链表的定义单链表初始化不带头结点的单链表初始化带头结点的单链表初始化单链表的插入按位序插入带头结点不带头结点指定结点的后插操作指定结点的前插操作单链表的删除按位序删除&#xff08;带头结点&#…

容器安全实践(三):信任、约定与“安全基线”镜像库

容器安全实践&#xff08;一&#xff09;&#xff1a;概念篇 - 从“想当然”到“真相” 容器安全实践&#xff08;二&#xff09;&#xff1a;实践篇 - 从 Dockerfile 到 Pod 的权限深耕 在系列的前两篇文章中&#xff0c;我们探讨了容器安全的底层原理&#xff0c;并详细阐述…

百度面试题:赛马问题

题目现在有25匹马和一个赛马场&#xff0c;赛马场有5条跑道&#xff08;即一次只能比较5匹马&#xff09;&#xff0c;并且没有秒表等计时工具&#xff0c;因此每次赛马只能知道这5匹马的相对时间而非绝对时间。问&#xff1a;如何筛选出跑的最快的3匹马&#xff1f;需要比赛几…

centos下安装Nginx(搭建高可用集群)

CentOS-7下安装Nginx的详细过程_centos7安装nginx-CSDN博客 centos换yum软件管理包镜像 CentOS 7.* 更换国内镜像源完整指南_centos7更换国内源-CSDN博客 VMware虚拟机上CentOS配置nginx后,本机无法访问 执行命令&#xff1a;/sbin/iptables -I INPUT -p tcp --dport 80 -j…

实时视频技术选型深度解析:RTSP、RTMP 与 WebRTC 的边界

引言&#xff1a;WebRTC 的“光环”与现实落差 在实时音视频领域&#xff0c;WebRTC 常常被贴上“终极解决方案”的标签&#xff1a;浏览器原生支持、无需插件、点对点传输、毫秒级延迟&#xff0c;这些特性让它在媒体和开发者群体中拥有了近乎神话般的地位。许多人甚至认为&a…

基于深度学习的阿尔茨海默症MRI图像分类系统

基于深度学习的阿尔茨海默症MRI图像分类系统 项目概述 阿尔茨海默症是一种进行性神经退行性疾病&#xff0c;早期诊断对于患者的治疗和生活质量至关重要。本项目利用深度学习技术&#xff0c;基于MRI脑部扫描图像&#xff0c;构建了一个高精度的阿尔茨海默症分类系统&#xff0…

54 C++ 现代C++编程艺术3-移动构造函数

C 现代C编程艺术3-移动构造函数 文章目录C 现代C编程艺术3-移动构造函数场景1&#xff1a;动态数组资源转移 #include <iostream> #include <vector> class DynamicArray { int* data; size_t size; public: // 移动构造函数&#xff08;关键实现&#xf…

Sping Boot + RabbitMQ :如何在Spring Boot中整合RabbitMQ实现消息可靠投递?

Spring Boot整合RabbitMQ实现消息可靠投递全解析 在分布式系统中&#xff0c;消息中间件是解耦、异步、流量削峰的核心组件。RabbitMQ作为高可靠、易扩展的AMQP协议实现&#xff0c;被广泛应用于企业级场景。但消息传递过程中可能因网络波动、服务宕机等问题导致消息丢失&#…

STAR-CCM+|K-epsilon湍流模型溯源

【1】引言 三维CFD仿真经典软件很多&#xff0c;我接触过的有Ansys和STAR-CCM两种。因为一些机缘&#xff0c;我使用STAR-CCM更多&#xff0c;今天就来回顾一下STAR-CCM中K-epsilon湍流模型的基本定义。 【2】学习地址介绍 点击链接User Guide可以到达网页版本的STAR-CCM 24…

osgEarth 图像融合正片叠底

* 需求&#xff1a;* 高程渲染图 RGB.tif、 山体阴影图 AMP.tif** 高程渲染图 rgb波段分别 乘以 山体阴影图r波段&#xff0c; 然后除以255(AI说 读取的纹理就已经归一化到了 0~1 范围&#xff0c;不用除以 255)。本人遥感知识匮乏。问了AI,以上 需求在许多商业软件上已实现。…

Java接口响应速度优化

在 Java 开发中&#xff0c;接口响应速度直接影响用户体验和系统吞吐量。优化接口性能需要从代码、数据库、缓存、架构等多个维度综合考量&#xff0c;以下是具体方案及详细解析&#xff1a;一、代码层面优化代码是接口性能的基础&#xff0c;低效的代码会直接导致响应缓慢。1.…

A Large Scale Synthetic Graph Dataset Generation Framework的学习笔记

文章的简介 作者提出了一个可扩展的合成图生成框架&#xff0c;能够从真实图中学习结构和特征分布&#xff0c;并生成任意规模的图数据集&#xff0c;支持&#xff1a; 节点和边的结构生成节点和边的特征生成特征与结构的对齐&#xff08;Aligner&#xff09; 它区别于GraphWor…

Android12 Framework读写prop属性selinux报错解决

文章目录问题描述解决过程相关文章问题描述 Android读prop值时&#xff0c;就算是system应用&#xff0c; 也需要selinux权限&#xff0c;否则会报错。 java代码如下 SystemProperties.get("ro.input.resampling", "")selinux报错如下 2025-06-28 17:57:…

【图文版】AIOT 小智 AI 聊天机器人 ESP32 项目源码图解

前言 小智 AI 聊天机器人是最近一个很火的开源项目&#xff0c;它借助LLM大模型以及TTS等AI的能力&#xff0c;通过自然语言来与其对话实现交互。它可以回答任何问题、播放音乐、背诵古诗&#xff0c;颇有未来AI机器人的雏形。 因为最近工作上的需要对其进行了研究&#xff0c;…

250821-RHEL9.4上Docker及Docker-Compose的离线安装

在 离线环境下 在 RHEL (Red Hat Enterprise Linux) 系统上安装 Docker 和 Docker Compose&#xff0c;需要提前在有网络的环境中下载相关 RPM 包及依赖&#xff0c;然后在目标机器上进行安装。以下是比较完整的步骤&#xff1a; 1. Docker及Docker-Compose离线安装 在 RHEL 9.…

react相关知识

1.类组件和函数组件&#xff08;1&#xff09;类组件import React, { Component } from react;class UserProfile extends Component {constructor(props) {super(props);this.state {userData: null,isLoading: true,};this.timerId null;}componentDidMount() {// 模拟 API…

算法第五十五天:图论part05(第十一章)

并查集理论基础并查集主要有两个功能&#xff1a;将两个元素添加到一个集合中。判断两个元素在不在同一个集合class UnionFind:def __init__(self, n):"""初始化并查集"""self.n nself.father list(range(n)) # 每个节点自己是根self.rank […

雨雾天气漏检率骤降80%!陌讯多模态车牌识别方案实战解析

一、行业痛点&#xff1a;车牌识别的天气敏感性据《智慧交通系统检测白皮书》统计&#xff0c;雨雾环境下传统车牌识别漏检率高达42.7%&#xff08;2024年数据&#xff09;。主要存在三大技术瓶颈&#xff1a;1.​​水膜干扰​​&#xff1a;挡风玻璃水渍导致车牌区域纹理模糊2…