当前位置: 首页 > news >正文

【C++】入门基础【下】

目录

  • 一、缺省参数
  • 二、函数重载
    • 1. 函数类型不同
    • 2. 参数个数不同
    • 3、函数类型顺序不同
  • 三、引用
    • 1、引用的概念和定义
    • 2、引用的功能
      • 2.1 功能1: 做函数形参,修改形参影响实参
      • 2.2 功能2: 做函数形参,减少拷贝,提高效率
      • 2.3 功能3:引用做返回值类型,修改返回对象
      • 2.4 功能4: 引用做返回值类型,减少拷贝,提高效率
    • 3、引用的特性
    • 4、`const`引用
    • 5、指针和引用的关系
    • 6、`inline`
    • 7、`nullptr`

【C++】入门基础【上】<–请点击
在这里插入图片描述
个人主页<—请点击
C++专栏<—请点击

一、缺省参数

  • 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省半缺省参数。(有些地方把缺省参数也叫默认参数)。

下面我们通过代码来认识一下缺省参数。

void func(int a = 10)
{cout << a << endl;
}

我们知道,在C语言中函数定义中变量是不能给值的,但在C++中却可以,这时候,给定的这个值就是缺省值,那这个参数就是缺省参数

那了解了缺省参数是什么,那么它在使用的时候又该怎么用能?请看代码:

func();
func(5);

此时我们要调用我们定义的函数,并且一个不给实参,一个给实参,让我们一起看一下代码的运行情况。
在这里插入图片描述
可以看到,没给实参的,函数默认使用了缺省值,而给定实参的,函数使用的是给定的实参而在C语言中,如果我们不给实参但函数又需要实参时,此时程序就会报错,所以C++的缺省参数优化了这一问题。

  • 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值C++规定半缺省参数必须从右往左依次连续缺省不能间隔跳跃给缺省值

如果你这样定义代码:

void func1(int a = 100, int b)
{cout << a<< " "<< b << endl;
}

会报错:
在这里插入图片描述
因为半缺省参数必须从右往左依次连续缺省

  • 带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参
    在这里插入图片描述
    像上图打算只给第二个传实参,跳过第一个时就会报错。

  • 函数声明和定义分离时缺省参数不能在函数声明定义同时出现规定必须函数声明缺省值

void func2(int a = 100);
void func2(int a = 100)
{cout << a << endl;
}

在这里插入图片描述
应改为:

void func2(int a = 100);
void func2(int a)
{cout << a << endl;
}

二、函数重载

我们知道C语言中不支持在同一作用域中出现同名函数的,它会报错,但在C++中是被允许的。C++支持在同⼀作用域中出现同名函数但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同如果全部相同那在C++中也是不被允许的

1. 函数类型不同

void func(int x, int y)
{cout << x + y << endl;
}void func(double x, double y)
{cout << x + y << endl;
}

测试结果:
在这里插入图片描述
从上面的测试结果,可以看出函数的调用成功而且正确

2. 参数个数不同

void f()
{cout << "hello world!" << endl;
}void f(int a)
{cout << a << endl;
}

测试结果:
在这里插入图片描述
注意:这里的第二个函数的参数不能带缺省值,如果带缺省值的话,当我们调用f()时,第一个函数和第二个函数都满足,此时程序会报出下面的错误:
在这里插入图片描述
这样编译器就会不知道调用谁,所以我们写重载函数的时候一定要注意区分它们,不能让它们存在歧义。

3、函数类型顺序不同

void fd(int a, double b)
{cout << "fd(int a, double b)" << endl;
}void fd(double b, int a)
{cout << "fd(double b, int a)" << endl;
}

测试结果
在这里插入图片描述

三、引用

1、引用的概念和定义

引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名编译器不会为引用变量开辟内存空间,它和它引用的变量共用同⼀块内存空间。

引用的用法类型& 引⽤别名 = 引⽤对象;

C++中为了避免引入太多的运算符,会复用C语言的⼀些符号,引用取地址使用了同⼀个符号&,大家注意使用方法角度区分就可以。

#include<iostream>
using namespace std;int main()
{int a = 10;//b和c是a的别名int& b = a;int& c = a;cout << a << endl;b++;cout << a << endl;c++;cout << a << endl;return 0;
}

结果:
在这里插入图片描述

#include<iostream>
using namespace std;int main()
{int a = 10;//b和c是a的别名int& b = a;int& c = a;//d也是a的别名int& d = b;cout << &a << endl;cout << &b << endl;cout << &c << endl;cout << &d << endl;return 0;
}

在这里插入图片描述
它们的地址都是一模一样的。

2、引用的功能

2.1 功能1: 做函数形参,修改形参影响实参

我们在C语言阶段实现交换函数的时候是用指针来实现的,现在引用也可以起到这样的作用。

#include<iostream>
using namespace std;void Swap(int& a, int& b)
{int tmp = a;a = b;b = tmp;
}int main()
{int x = 10;int y = 20;Swap(x, y);cout << "x=" << x << endl;cout << "y=" << y << endl;return 0;
}

交换结果:
在这里插入图片描述

2.2 功能2: 做函数形参,减少拷贝,提高效率

不使用引用时,值传递会触发原对象的拷贝,如果对象比较大(如包含复杂成员,动态内存或嵌套结构),拷贝操作的时间和空间开销显著。

而当使用引用时,引用就是原对象的别名,传递时不会产生拷贝,直接操作原始对象,省去了拷贝构造的开销,尤其对大型对象效果显著。

2.3 功能3:引用做返回值类型,修改返回对象

#include <iostream>
using namespace std;int& func(int* a, int n)
{return a[n];
}void print(int* a,int n)
{for (int i = 0;i < n;i++){cout << a[i]<<" ";}cout << endl;
}int main()
{int a[11];for (int i = 0;i < 11;i++){a[i] = i + 1;}print(a, 11);func(a, 2)+=10;print(a, 11);
}

从上面代码中可以看出,func函数的返回值类型是引用充当的,这样的好处是可以更改返回对象。

运行结果:
在这里插入图片描述
从运行结果可以看出数组中下标为2的位置被更改了。

2.4 功能4: 引用做返回值类型,减少拷贝,提高效率

我们知道当函数调用结束时函数栈帧会被销毁,那么其中定义的局部变量的生命周期也就结束了自然也会被销毁,当函数的返回值是函数中定义的局部变量时,编译器会将返回值拷贝下来,然后储存在临时变量中作为返回值

假设有如下函数:

int func()
{int set = 10;return set;
}

在这里插入图片描述
那既然传引用返回可以更改返回对象,那传值返回可以吗?下面我们来试一下。我们还是使用传引用返回的代码,但是将传引用返回改为传值返回。

发现程序会报出以下错误:
在这里插入图片描述
这是因为函数返回的是值,而在函数返回值之前同样会将返回的值拷贝下来,储存在临时变量中进行返回,而临时变量它具有常性,是不可修改的,所以才会报出以上错误。

下图是它们三者之间的区别
在这里插入图片描述
所以说引用做返回值类型,减少了拷贝,提高了效率。

产生临时变量的情况:

出现类型转换的时候也会产生临时变量,像double类型d=1.5,转化为int类型的x这种情况,会产生一个临时变量存储转换结果,然后再将临时变量赋值给x

产生临时变量的情况有以下几种:类型转换、值传递、表达式求值等等。
其中值传递就是我们上图展示的情况,表达式求值例如:

int a = 1;
int b = 9;
int c = a + b * 10;

计算b*10时生成临时int,再与a相加生成临时int,最后赋值给c

不安全的引用写法:

int& func()
{int set = 10;return set;
}

原因:set是局部变量,func结束后,set就销毁了,返回它的别名本质也是一种类似野指针的行为。

3、引用的特性

  • 引用在定义时必须初始化;
  • ⼀个变量可以有多个引用;
  • 引用⼀旦引用⼀个实体,再不能引用其他实体。

引用无法改变指向所以在链式结构中无法替代指针,这样的场景下必须使用指针。

4、const引用

可以引用⼀个const对象,但是必须用const引用const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,而不能放大

权限放大的错误样例

const int x = 10;
int& y = x;

在这里插入图片描述
最初定义的x的本意就是x不可更改,但却使用int&引用x这就导致了冲突,它的权限被放大了。

正确的使用:

const int x = 10;
const int& y = x;

注意以下这种情况中没有权限的放大

const int x = 10;
const int& y = x;
int z = y;

有人在学习完成引用后他们会认为上面这段代码涉及到了权限的放大,但上面代码的意图是定义一个变量z,并将y值赋值给z,只是一个简单的赋值操作,注意不要混淆了。

权限的缩小是被允许的

就像老师对你说,下课不准出校门,但你连教室门都不出这种情况一样。

int x = 10;
const int& y = x;

5、指针和引用的关系

  • 语法概念上引用是⼀个变量的取别名,不开空间,指针是存储⼀个变量地址,要开空间。
  • 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  • 引用在初始化时引用⼀个对象后,就不能再引用其他对象;而指针可以在不断地改变指向对象。
  • 引用可以直接访问指向对象,指针需要解引用才能访问指向对象。
  • sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8个字节)
  • 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。

指令汇编角度引用是使用指针实现的。

int x = 10;
int& y = x;
int* py = &x;

转到指令汇编
在这里插入图片描述
引用的下面两行是引用语句的汇编代码,而指针下面两行是指针语句的汇编代码。我们从上图可以看出两者一模一样,所以进一步印证了引用是使用指针实现的。

6、inline

inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就不需要建立函数栈帧了,就可以提高效率

C语言实现的宏函数也会在预处理时替换展开**,但是宏函数实现很复杂且很容易出错,还不能调试,C++设计inline目的就是替代C语言的宏函数。**

正常的函数:

int add(int x, int y)
{int sum=x + y;return sum;
}

执行语句:int ret = add(2, 3);时,它的反汇编代码是这样的:
在这里插入图片描述
图片中有一个call指令,这个指令是调用add函数,说明函数没有在预处理时展开

inline修饰的函数:

inline int add(int x, int y)
{int sum=x + y;return sum;
}

执行语句:int ret = add(2, 3);时,它的反汇编代码是这样的:
在这里插入图片描述
从上图可以看出它没有调用函数,也就是没有创建函数栈帧,而是在预处理阶段就展开了,像C语言的宏函数一样。这样就可以提高效率。

inline对于编译器而言只是⼀个建议,也就是说,你加了inline,编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。

inline不建议声明定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。我们知道编译器生成可执行程序会经过预处理、编译、汇编、链接等过程,而在链接过程代码中的普通函数的地址需要到XXX.o符号表中去寻找,因为他们不会展开所以它们的函数地址会进入符号表中,当你正确使用inline修饰函数,即声明和定义不分离时,inline修饰的函数调用的地方已经正常展开了,而当你声明和定义分离时,由于inline修饰的函数的地址它本身不会进入XXX.o符号表,又因为你没有inline修饰的函数定义,此时函数调用的地方就没有正常展开编译器寻找地址的时候又找不到,此时就会报链接错误

声明和定义分离的错误情况:
F.h

#include <iostream>
using namespace std;inline int add(int x, int y);

F.cpp

#include "F.h"inline int add(int x, int y)
{int sum = x + y;return sum;
}

test.cpp

#include"F.h"int main()
{int ret = add(2, 3);return 0;
}

链接错误
在这里插入图片描述
当你声明和定义不分离,再将F.cpp中的定义删去,(因为此时F.h中已经有了,如果你这里不删除的话它依旧会报错,因为出现了两个一摸一样的函数主体),即:

F.h

#include <iostream>
using namespace std;inline int add(int x, int y)
{int sum = x + y;return sum;
}

这样就可以正常展开了。

拓展inlinestatic所修饰的函数都具有内部链接属性,不会进入XXX.o符号表中,所以不会造成C2084类型错误:
在这里插入图片描述

7、nullptr

NULL实际是⼀个宏,在传统的C头文件stddef.h中,可以看到如下代码:

#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif

上面这段代码是条件编译指令,感兴趣的小伙伴点击–>【C语言】编译和链接、预处理详解

C++NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,例如:

#include <iostream>
using namespace std;void f(int n)
{cout << "f(int n)" << endl;
}
void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0);f(NULL);return 0;
}

这段代码的执行结果是:
在这里插入图片描述本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL定义成0,调用了f(int x),因此与程序的初衷相悖f((void*)NULL);调用会报错
在这里插入图片描述
为了解决这个问题C++11中引入了nullptrnullptr是⼀个特殊的关键字nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

#include <iostream>
using namespace std;void f(int n)
{cout << "f(int n)" << endl;
}
void f(int* ptr)
{cout << "f(int* ptr)" << endl;
}int main()
{f(0);f(NULL);f(nullptr);return 0;
}

在这里插入图片描述
这样就解决了这个问题。所以在C++初始化指针为空,会用nullptr这个关键字初始化。

总结:
以上就是本期博客分享的全部内容啦!如果觉得文章还不错的话可以三连支持一下,你的支持就是我前进最大的动力!
技术的探索永无止境! 道阻且长,行则将至!后续我会给大家带来更多优质博客内容,欢迎关注我的CSDN账号,我们一同成长!
(~ ̄▽ ̄)~

相关文章:

  • 编译 C++ 报错“找不到 g++ 编译器”的终极解决方案(含 Windows/Linux/macOS)
  • 2025最新系统 Linux 教程(六)
  • HTML5 服务器发送事件 (Server-Sent Events):实现网页自动获取服务器更新
  • 第53.5讲 | 小项目实战:用 SHAP 值解释农作物产量预测模型 [特殊字符][特殊字符]
  • Next.js v15 eslint 规则配置
  • Spring Boot知识点详解
  • 27、Session有什么重⼤BUG?微软提出了什么⽅法加以解决?
  • 【基础】Node.js 介绍、安装及npm 和 npx功能了解
  • 如何快速高效学习Python?
  • 界面开发框架DevExpress XAF实践:如何在Blazor项目中集成.NET Aspire?(二)
  • (第三篇)Springcloud之Ribbon负载均衡
  • 精益数据分析(21/126):剖析创业增长引擎与精益画布指标
  • 从码云上拉取项目并在idea配置npm时完整步骤
  • 【Spring Boot】深入解析:#{} 和 ${}
  • 算法笔记.spfa算法(bellman-ford算法的改进)
  • 五、web自动化测试01
  • 电脑怎么强制退出程序回到桌面 详细操作步骤
  • 为什么要提出Null-text Inversion
  • 力扣热题100题解(c++)—矩阵
  • 深入详解人工智能数学基础——概率论中的KL散度在变分自编码器中的应用
  • 点燃“文化活火”,上海百年街区创新讲述“文化三地”故事
  • 旧衣服旧纸箱不舍得扔?可能是因为“囤物障碍”
  • 文化体验+商业消费+服务创新,上海搭建入境旅游新模式
  • 怎样更加贴近中国消费者,运动品牌给出“本地化”选择
  • 马上评|起名“朱雀玄武敕令”?姓名权别滥用
  • 华夏银行青岛分行另类处置不良债权,德州近百亩土地被神奇操作抵押贷款