IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++第二话----->缺省参数和函数重载和extern “C“详解 -> 正文阅读

[C++知识库]C++第二话----->缺省参数和函数重载和extern “C“详解

本文主要内容
1.什么是缺省参数
2.什么是函数重载
3.C++为什么能够支持函数重载而C语言不行
4.什么是extern “C”

正文:
一.缺省参数:
我们知道,在C语言里我们可以这样定义一个函数:

void Show(int a)
{
  printf("%d\n",a);
}

那么C++不仅可以支持C语言方式定义函数,而且C++本身还有自己的一套函数的形参定义方式:缺省参数,简单来讲就是指定形参会给一个默认的初始值。

//C++允许在函数形参部分定义一个默认的初始值
void Show(int a=20)
{
   cout<<a<<endl;
}

接下来我们用两种方式来调用show函数看看会发生什么:

#include<iostream>
using namespace std;
void Show(int a = 20)
{
	cout << a << endl;
}
int main()
{   
   cout<<"不传参数调用";
	Show();
	cout<<endl<<"传递参数调用";
	Show(5);
	return 0;
}

在这里插入图片描述
没有调用参数的版本打印出了20,使用的是默认的参数,而第二个我们传递了1个参数5,打印了5!说明只要我们给默认实参的位置传递参数,那么这个参数就会被立即替换!做个形象的比喻,这个默认实参就好像沸羊羊一样,只有在美羊羊找不到喜羊羊的时候,才会想到沸羊羊,就是一个备胎。
??关于缺省参数的给定方式,分为全缺省和半缺省的方式,那么全缺省比较好理解,它的代码如下:

void Show(int a=5, int b = 10, int c = 20)
{
	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
}
int main()
{
	//对应的调用方式
	//不带参数
	Show();
	//只带一个参数
	Show(1);
	//带两个参数
	Show(1, 3);
	//带三个
	Show(1, 2, 3);
	return 0;
}

??有缺省值的函数参数传参一定要是从左到右依次顺序排列,你不能这样传递两个参数:

//错误的传参--->必须是从左到右依次传递
	Show(1, , 3);

讲完了全缺省,接下来我们看一看半缺省参数,相对于全缺省来讲,半缺省参数的细节更多,更复杂,下面是一个半缺省的函数:

	void Show(int a, int b=1, int c=2)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

??对应的传参方式:

//带一个参数
	Show(1);
	//带两个
	Show(1, 3);
	//带3个
	Show(1, 3, 2);

那么在半缺省参数函数里面,不能间隔的缺省参数,缺省参数必须是从右到左是连续的!举个简单的例子:

//正确:默认参数从右到左连续缺省
void Show(int a, int b=1, int c=2)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
//正确:默认参数从右到左连续缺省
void Show(int a,int b,int c=3)
{
     cout << a << endl;
	 cout << b << endl;
	 cout << c << endl;
}
//错误:默认参数必须从右到左是连续缺省的
void Show(int a=2, int b, int c=1)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

那么你可能会好奇默认参数有什么作用?还记得我们先前写得顺序表吗?在顺序表的初始化函数我们就可以使用默认参数来初始化一个初始容量为4的顺序表

struct SeqList
{
	int* a;
	size_t size;
	size_t capacity;
	void SeqListInit(SeqList* ps,int n=4);
};
void SeqList::SeqListInit(SeqList* ps, int n = 4)
{
	ps->a = (int*)malloc(sizeof(int) * n);
	ps->size = 0;
	ps->capacity = n;
}

??当然这里的写法涉及到了C++引入的类的机制,这里我们不做重点讨论,只是介绍了一下默认参数可以怎么使用。具体其他的代码实现细节我会放在类和对象的章节去介绍.
缺省值不能同时在函数声明和函数定义种出现!比如下面这个例子:

//在func.h
#pragma once
#include<iostream>
using namespace std;
void func(int a = 20);
//func.cpp
#include "func.h"
void func(int a = 20)
{
	cout << a << endl;
}
//test.cpp
#include "func.h"
int main()
{
	func();
	return 0;
}

编译以后,报错,报错信息如下:
在这里插入图片描述
报错说我们对func重定义,说明说声明和定义不能同时出现缺省参数!这也很好理解,因为C++的官方库担心你声明和定义给定的缺省值不相同导致二义性,所以直接就不让你同时在声明和定义出现缺省参数。既然声明和定义不能同时出现,那么我在声明的地方给默认参数,定义不给定,能否编译通过?我们马上上手实践一下:

//在func.h
#pragma once
#include<iostream>
using namespace std;
void func(int a = 20);
//func.cpp
#include "func.h"
void func(int a )
{
	cout << a << endl;
}
//test.cpp
#include "func.h"
int main()
{
	func();
	func(5);
	return 0;
}

编译运行一下:结果如下:
在这里插入图片描述
程序输出了正确的结果,这种方法是合法的!那么我们尝试声明不给缺省参数,定义给缺省参数,编译看看会发生什么:

//在func.h
#pragma once
#include<iostream>
using namespace std;
void func(int a );
//func.cpp
#include "func.h"
void func(int a=20 )
{
	cout << a << endl;
}
//test.cpp
#include "func.h"
int main()
{
	func();
	func(5);
	return 0;
}

编译结果如下:
在这里插入图片描述??我们发现编译过不去,这种方式是不对的!那么为什么会不对呢?这时候就要联系前面程序的编译的整个过程。我们知道func.h在预处理阶段被展开,编译器在把c++代码翻译成汇编代码的时候,编译器只会向上扫描找到的没有带默认参数的版本,后面就会认为这个函数是没有默认参数的。然后它碰到这个函数带默认参数的版本它就不认识导致编译失败!所以只在定义给定缺省参数是没有实质性作用的!
二.函数重载:
??在C语言中,如果我们需要交换两个整数,两个字符,两个浮点数要这么写:

void SwapInt(int* pa, int* pb)
{
	int x = *pa;
	*pa = *pb;
	*pb = x;
}
void SwapChar(char* pa, char* pb)
{
	char x = *pa;
	*pa = *pb;
	*pb = x;
}
void SwapDouble(double* pa, double* pb)
{
	double x = *pa;
	*pa = *pb;
	*pb = x;
}

??同样都是交换函数,但是我们要针对不同的数据类型写不同的函数,这些函数名还需要有区分,这大大加重了程序员的工作量,都叫swap不好吗?所以为了解决这个问题,C++引入了函数重载的机制。
??所谓的函数重载用一个成语来讲就是—>一词多义,就是一个词有多种含义。接下来我们就用求和函数作为例子.

int Add(int x, int y)
{
	return x + y;
}
double Add(double x, double y)
{
	return x + y;
}
double Add(int x, double y)
{
	return x + y;
}
double Add(double x, int y)
{
	return x + y;
}

进行编译:
在这里插入图片描述
编译成功,这就是重载的作用:虽然还是写了好几份代码,但是至少我们不要在为函数起名为难。那么函数重载的规则如下:

1.函数的参数数量不同构成重载
2.函数的参数的类型不同构成重载
3.只有返回值不同不能构成重载
4.只有形参名字顺序不同的函数不构成重载

相信对于1,2大家可能都比较好理解,但是3,4就可能没那么直观,那么来看这个例子:

//这两个Add只有返回值不同,不构成重载
int Add(int x, int y)
{
	return x + y;
}
double Add(int x, int y)
{
	return x + y;
}
//这两个函数只是形参换了名字,也不构成重载
double Add(int x, double y)
{
	return x + y;
}
double Add(int y, double x)
{
	return x + y;
}

形参名顺序不同不能构成重载,但是形参的类型的顺序不同可以构成重载!
那么接下来我们就从底层原理来分析一下为什么C++能够支持函数重载,我们使用的是Linux平台(Windows太诡异,不便观察)接下来我使用的软件叫做Xshell,是一个能够连接远程服务器的软件。接下来我就通过这个软件来远程连接linux服务器来演示操作
??这里我们简要回顾一下整个程序最后是怎么变成一个可执行文件的。首先第一步是预处理---->展开头文件和宏替换,第二步是生成汇编代码和对应的符号表(存储相关函数的地址),第三步是汇编---->生成对应的二进制机器指令,最后一步是链接各个二进制文件。接下来我们就来看一看C++和C是怎么编译文件的。

这里我们分别写如下的文件:
1.func.h---->存放func函数的声明
2.func.cpp---->实现func函数的cpp文件
3.test.cpp---->调用func函数的cpp文件
4.func.c---->实现func的的c文件
5.test.c---->调用func的c文件

//func.h的文件
#include<stdio.h>
void func(int x,double y);
void func(double x,int y);   
//func.c的文件
#include<stdio.h>
void func(int x,double y)
{
     printf("%d\n",x);
     printf("%f\n",y);
}
   void func(double x,int y)
   {
     printf("%f\n",x);
    printf("%d\n",y);                                                                                  
   }
//main.c
#include "func.h"
int main()
{
    func(4,2.5);
    func(2.5,4);                                                                                       
    return 0;
}

那么程序运行结果如下:
在这里插入图片描述
显然,C语言是不支持重载的!这里是编译阶段就出了问题,我们可以使用如下的指令来观察汇编代码:

objdump -S 编译的可执行文件

在这里插入图片描述
不难看出,C语言修饰在生成汇编的符号表的时候,是直接用函数的名字,这就是为什么C语言不能支持重载的原因!
接下来,我们来观察C++是怎么对重载进行支持的:

在这里插入图片描述
在这里插入图片描述
我们就拿第一个函数的命名来分析:

_z4funcid:首先这里的_z是Linux下特定的修饰前缀,我们不用去关系他,其次func就是函数的名字,后面的数字就是函数名字的长度,最重要的是后面的两个字母i,d分别是函数参数类型的首字母!也就是C++在生成函数的符号表的时候,把参数的类型也记录上去了!这就是为什么C++能够支持重载的原因!

总结:Linux下C++函数修饰规则:_z+函数名+函数名长度+参数类型首字母(windows下大致相同)
三.extern “C”:
??首先我们知道C++项目调用C++的库是没有问题,C语言调C语言的库也是没有问题的。但是在特定的生产环境下,C++可能需要调用C语言的库,C语言需要调用C++的库,为了解决调用的时候可能出现的冲突问题。所以才引进了这个extern “C”
从先前我们对函数重载的原理分析我们不难得出,C++和C语言起冲突的根本原因就是函数修饰规则不同,C语言不能直接按照C++的修饰规则修饰的函数同样C++不能直接识别C语言修饰规则修饰的函数
但是,C++是认识C语言的修饰规则的,那么在碰到C语言修饰的程序,那么可以指定C++使用C语言的方式去修饰函数和链接函数—>这就是extern "C"的使用原理。那么接下来我们看一看怎么在一个C++项目里面调用C语言程序:首先我们要明确,C++调用C语言或者是C语言调用C++调用的都是语言编译完生成的库而不是可执行程序!所以接下来我们要生成一个C语言的静态库供C++使用

第一步右键属性

在这里插入图片描述

第二步按照图示选择静态库:

在这里插入图片描述

第三步:新建一个C++项目,并在新的项目里面包含原来静态库所在的路径
在这里插入图片描述

接下来我们尝试调用先前写好的函数:

int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	QueuePush(&q, 2);
	QueuePush(&q, 3);
	QueuePush(&q, 4);
	printf("%u\n", QueueSize(&q));
	QueueDestroy(&q);
	return 0;
}

在这里插入图片描述
发生了链接错误,因为我们还少了一步配置。

依旧是右键属性,找到如下的选项:

在这里插入图片描述

点击这个附加库目录,选择编辑添加已有的静态库

在这里插入图片描述
接下来打开输入,添加依赖。
在这里插入图片描述
接下来我们编译,结果如下:
在这里插入图片描述
通常这种出现Link都是函数在链接的时候出现了错误,原因就是C++按照自己的函数修饰规则去链接C语言的库自然就是链接不上!所以接下来我们的extern "C"就派上用场了。

extern "C"
{
#include "../../QueueLib/QueueLib/Queue.h"
}
int main()
{
	Queue q;
	QueueInit(&q);
	QueuePush(&q, 1);
	printf("%d\n", QueueEmpty(&q));
	QueueDestroy(&q);
	return 0;
}

程序运行结果如下:
在这里插入图片描述
??虽然C++不能够直接识别C语言修饰的函数,但是C++能够知道C语言修饰函数的方式,而extern “C”就是告诉C++用C语言的方式解析C语言的官方库!

**注意!只有C++才支持extern “C”**在C语言中没有这种语法!

??那么了解了C++调用C语言,那么有没有C调用C++呢?答案是肯定的!接下来我们就开始着手实现一个C调用C++的项目。
在这里插入图片描述
同样,C语言也不能够识别C++的函数修饰规则所以导致链接错误!那么要想解决这个问题,只能让C++按照C的方式修饰函数,所以我们可以在C++文件里加上extern “C”但是这里又有一个问题,头文件在C语言文件那里也展开了!而C语言并不认识这个extern “C”,所以为了解决这个问题我们需要使用条件编译和一个宏.

__cplusplus—>这是C++文件在编译的时候默认会带有的宏,所以我们可以这样妙用条件编译:

#ifdef __cplusplus
//只有是C++文件猜编译extern "C"
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif
//初始化
EXTERN_C void StackInit(Stack* ps);
//释放
EXTERN_C void StackDestroy(Stack* ps);
//插入
EXTERN_C void StackPush(Stack* ps, STDataType x);
//删除
EXTERN_C void StackPop(Stack* ps);
//取栈顶数据
EXTERN_C STDataType StackTop(Stack* ps);
//判断栈是否为空
EXTERN_C bool StackEmpty(Stack* ps);
//获取栈元素的个数
EXTERN_C size_t StackSize(Stack* ps);

这样我们的C就可以调用C++的接口了!那么这么写确实有一点繁琐,我们还能有更加简单的方式来替代。

#ifdef __cplusplus
extern "C"
{
#endif
	void StackInit(Stack* ps);
	//释放
	void StackDestroy(Stack* ps);
	//插入
	void StackPush(Stack* ps, STDataType x);
	//删除
	void StackPop(Stack* ps);
	//取栈顶数据
	STDataType StackTop(Stack* ps);
	//判断栈是否为空
	bool StackEmpty(Stack* ps);
	//获取栈元素的个数
	size_t StackSize(Stack* ps);
#ifdef __cplusplus
}
#endif

这种写法就比较巧妙,只有是C++项目的时候,里面的函数才会以C++的方式修饰,而如果只是一个C语言的项目,那么extern“C”和括号就不会被编译,这时候 就相当于是纯粹的C语言的函数声明,这样的做法在函数数量多的时候会非常的简洁。
总结:
1.C++支持缺省参数,这个参数不能同时出现在声明和定义里面,并且必须是从右到左连续缺省。
2.C++支持重载以及背后的底层原理
3.extern “C”—>处理C++程序和C语言程序之间互相调用起冲突的问题的解决方法。

??以上就是本篇文章的主要内容,如果不足和错误之处,希望能够指出。大家互相勉励共同进步

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-11 16:14:51  更:2022-05-11 16:15:08 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:34:11-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码