【C++】C++入门知识详解(下)

news2024/9/22 4:14:14

大家好~我们接着【C++】C++入门知识详解(上)-CSDN博客来介绍另一些C++入门基础知识。

1.缺省值和缺省参数

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

有些地方把缺省参数也叫做默认参数。其实并不复杂,看下面的函数例子就能理解了。

void Func(int a = 0)
{
	cout << a << endl;
}

我们定义这个函数时会给形参一个默认值,这个默认值其实就是缺省值,当我们调用这个函数时,传参和不传参情况如下。

Func();
Func(1);

我们可以看到,不传实参时,函数就用原本的a=0这个默认值作为形参;当给函数传参时,传的什么,形参就是什么。这里的0就是缺省值,a就是缺省参数。 

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

//全缺省
void Func1(int a = 1, int b = 2, int c = 3)
{
	cout << "a = " << a << " ";
	cout << "b = " << b << " " << "c = " << c << endl;
}
//半缺省
void Func2(int a, int b = 2, int c = 3)
{
	cout << "a = " << a << " ";
	cout << "b = " << b << " " << "c = " << c << endl;
}

而且,带缺省参数的函数调用,C++规定必须左往右依次给实参,不能跳跃、不能间隔给实参

 我们先看全缺省的函数Func1。有如下四种调用

Func1();       //什么都不传
Func1(4);      //传一个实参
Func1(4, 5);   //传两个实参
Func1(4, 5, 6);//传三个实参

半缺省的函数Func2,因为第一个参数没有给缺省值,所以必须传一个实参。有如下3种调用

Func2(4);      //传一个实参
Func2(4, 5);   //传两个实参
Func2(4, 5, 6);//传三个实参

 缺省参数在实践中是非常有意义和价值的,在学习中我们可以体会到。

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

 比如在头文件Func.h中,我们声明上面的函数Func1和Func2

void Func1(int a = 1, int b = 2, int c = 3);
void Func2(int a, int b = 2, int c = 3);

源文件test.cpp中定义Func1和Func2 

void Func1(int a, int b, int c)
{
	cout << "a = " << a << " ";
	cout << "b = " << b << " " << "c = " << c << endl;
}
void Func2(int a, int b, int c)
{
	cout << "a = " << a << " ";
	cout << "b = " << b << " " << "c = " << c << endl;
}

如果在test.cpp文件中函数定义的参数还是出现了缺省值,就会报类似下面这样的错误,显示重定义默认参数。

 2.函数重载

C++支持在同一作用域出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。返回值不能作为函数重载的条件。

这样C++函数调用就表现出了多态行为,使用更灵活。C语言是不支持同一作用域出现同名函数的。

2.1 参数类型不同

同样是实现两个数的相加,一个是整数的相加,一个是小数的相加,在C语言中我们可能就会分成两个不同的函数来实现,比如add1实现整数的相加,add2实现小数的相加,有了函数重载,我们就可以用add一个函数名来实现这两个函数。

int add(int a, int b)  
{
	cout << "int add(int a, int b)" << endl;
	return a + b;
}

double add(double a, double b)
{
	cout << "double add(double a, double b)" << endl;
	return a + b;
}

同名函数?那怎么调用呢?

其实,在函数调用时,会根据我们传给函数的实参的类型自动调用相应的函数

add(1, 2);     //传int型实参
add(1.1, 2.2); //传double型实参

 单看函数名,我们就觉得自己在用同一个函数,其实并不是一个函数。这样函数用起来就很有可读性。

2.2 参数顺序不同

函数重载的参数也可以是类型相同顺序不同,看下面两个函数

void f(char c, int i)   //先char,后int
{
	cout << "void f(char c, int i)" << endl;
}
void f(int i, char c)   //先int,后char
{
	cout << "void f(int i, char c)" << endl;
}

调用时也是一样,根据传给函数的实参的类型自动调用相应的函数

2.3 参数个数不同

名字相同,参数个数不同也是函数重载。普通的例子在这里就不列举了,我们说一下特殊的。

看下面两个函数,一个函数无参,一个函数带参,这两个函数构成函数重载吗?

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

 构成函数重载。但是调用时会存在歧义

当我们调用时给函数传参,就会自动调用有参数的那个函数,这个没问题

那当我们不给函数传参时,应该调用哪个呢?含缺省参数的函数无参也可以调用啊。所以此时函数调用就会有歧义,程序会报错

 所以大家在学习中要注意这些问题。

3.引用

3.1 引用的概念、定义及特征

引用不是新定义一个变量,而是给已存在的变量起个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。符号为&。(类型& 引用别名 = 引用对象

 引用的出现其实针对的是C语言中的指针。引用其实很好理解,比如在水浒传中,林冲外号“豹子头”,李逵江湖人称“黑旋风”等,这些就是他们的别名,而不管是豹子头还是林冲,都代表的是同一个人,引用也是一个道理。我们来看具体程序。

int a = 0;
int& b = a; //给a取别名为b
int& c = a; //给a再取一个别名为c

我们可以不止取一个别名,一个变量可以有多个别名。我们还可以给别名取别名,如下。

int& d = b; //给别名b取别名为d

 此时d相当于还是a的别名,我们看看a,b,c,d的地址,会发现它们的地址一摸一样。

这就验证了这句话,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间 。如果我对d加加呢?

会发现a,b,c,d全都加了,这也证明a,b,c,d是同一个,都是a。

引用的特征:

1.引用在定义时必须初始化

2.一个变量可以有多个引用

3.引用一旦引用一个实体,就不能再引用其他实体(引用不能改变指向

 前两个特征很好理解,我们重点说一下第三个特征。先看看下面这段代码。

int a = 10;
int& b = a;

int c = 20;
b = c;

思考一下,这里 b = c 是什么意思?是把b变成c的别名?还是c赋值给b?

这里其实是赋值,把c的值赋给b,b是a的别名,也就是把c的值赋给a。我们把a,b,c都打印出来看看 。

 这也证明引用不能改变指向。我们还可以通过观察地址来看引用是否改变了指向。

可以看到a,b地址相同,b还是a的别名,和a共用同一块空间,而c只是赋值给b。 

3.2 引用的应用

引用在实践中主要用于传参和做返回值,准确说就是用在函数。

1.函数传参其实是把实参拷贝给形参,形参是实参的临时拷贝,有了引用就可以减少拷贝

2.引用从语法上来说,形参是实参的别名,形参也不会额外开辟空间,效率就得到了提高。

3.在函数中我们知道,形参的改变不影响实参,想要通过函数改变实参就要传地址对吧,学了引用后我们大部分情况就可以不用指针传地址实现了。

举一个最简单例子,函数实现交换两个数,在C语言中应该像下面这样传地址才能实现实参的改变。

void Swap(int* px, int* py)
{
	int temp = *px;
	*px = *py;
	*py = temp;
}

当我们学了引用,就不需要指针了,我们直接将形参看作实参的别名

void Swap(int& rx, int& ry)
{
	int temp = rx;
	rx = ry;
	ry = temp;
}

 

这里,rx就是x的别名,rx的改变就是x的改变;ry就是y的别名,ry的改变就是y的改变,rx与ry的交换就实现了x与y的交换 。

在能使用指针的地方比如说栈,队列等都可以尝试用引用,会方便很多。引用做返回值我们后续再讨论。 

3.3 coust引用 

const引用被const修饰的量

一个被const修饰的变量a,怎么给它取别名?

const int a = 10;

直接int& ra = a;绝对是不可以的。这是一个经典的权限放大。被const修饰的变量其实就是让这个变量变得只能读不能写,而int类型是可读可写的,别名ra也可读可写,a被const修饰只能读,而现在来一个可读可写的别名?这就让a权限放大了。

引用权限可以缩小,不能放大。那应该怎么给const修饰的变量取别名呢?像下面这样。

const int& ra = a;

const引用正常变量 

没有被const修饰的变量b,可以直接用int& rb = b;来取别名

int b = 20;

那可以像下面这样吗?

const int& rb = b;

可以,这里就是引用权限缩小,一个可读可写的b,别名rb只能读。

但是这并不意味着b的权限缩小了,用b这个名字的时候还是可读可写,但是用rb这个名字的时候就只能读,但rb还是b,只是权限不同,就像你在家可以想脱鞋就脱鞋,在教室就不能随意脱鞋,但你还是你,只是权限不一样。

const引用常量

const引用还可以给常量取别名。比如说我要给10这个常数取别名。如果不加const就不行。

const int& rc = 10;

const引用临时对象 

 const引用还可以给临时对象取别名。比如给a+b这个表达式取别名,如果不加const就不行。

//这里a和b有没有const无所谓
const int a = 10;
int b = 20;
const int& rd = a + b;

因为表达式的结果会存在临时对象里面,临时对象就是编译器需要一个空间暂存表达式的求值结果时,临时创建的一个未命名的对象,这个临时对象具有属性。如果是 int rd = a + b;意思就是把a+b结果的临时对象拷贝给rd

再看下面这个,我们怎么给double类型的d取一个int类型的别名?

double d = 3.14;
int i = d;

上面这个d并不是直接赋值给i,d和i类型不同,这里叫隐式类型转换,隐式类型转换中间会产生临时对象存储。所以这里我们也不能直接用int& ri = d;取别名,还是要加const。

double d = 3.14;
const int& ri = d; //给d取int类型的别名

(临时对象的生命周期会和别名的生命周期一样)

3.4 引用和指针的区别

C++中指针和引用就像两个性格迥异的亲兄弟,指针是哥哥,引用是弟弟,在实践中他们相辅相成,功能有重叠性,但是各有自己的特点,互相不可替代。

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

4.inline内联

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

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

(3)vs编译器debug版本下默认是不展开inline的,这样方便调试,debug版本想展开需要设置下面两个地方,

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

(5)inline不建议声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。

5.nullptr空指针

NULL实际是一个宏,在.c文件中定义一个指针,赋值为NULL,然后按住ctrl,点击这个NULL,就会跳转到如下代码处。

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

 C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,本想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成O,调用了f(int x),因此与程序的初衷相悖。f((void*)NULL);调用会报错。

插入一个小知识

在C语言中void*的指针是可以转成任意类型的,比如

void* p1 = NULL;
int* p2 = p1;

 而在C++中语法更加严格,上面的p1想赋值给p2,必须强制类型转换成int*。

void* p1 = NULL;
int* p2 = (int*)p1;

我们来看具体例子,下面这个函数重载,实参传入NULL时,会调用哪个函数?第二个?

void f(int x)
{
	cout << "void f(int x)" << endl;
}
void f(int* p)
{
	cout << "void f(int* p)" << endl;
}

 

结果调用了第一个函数。证明NULL在C++中其实是0。 

C++11中引入nullptr,nullptr是一个特殊的关键字,nullptr是一种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。

简而言之,就是在C++中的空指针变成了nullptr,不是NULL

本篇到这里就结束了,拜拜~

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ChatGPT:GPT,GPT2,GPT3,Prompt

1&#xff0c;GPT 1.1&#xff0c;GPT结构 GPT is short for Generative Pretrained Transformer。其实GPT和BERT的区别就写在他们的脸上。GPT是Generative的&#xff0c;目的就是要生成。它是一个预训练的Transformer&#xff0c;因为目的就是要生成&#xff0c;所以是Decode…

C++ unordered_map

1. unordered系列关联式容器 在C98 中&#xff0c; STL 提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可达到 &#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点非常多时&#xff0c;查询效率也不理想。最好的查询是&#xff0c…

亚马逊VC账号特权及SC升级至VC策略解析——WAYLI威利跨境助力商家

亚马逊的VC(Vendor Central)账号和SC(Selling on Amazon)账号在运营策略上有显著差异&#xff0c;这些差异主要体现在账号安全性、数据获取、广告投放以及费用结构等方面。 VC账号特权 1、高安全性&#xff1a;VC账号相较于SC账号具有更高的安全性。由于亚马逊平台对不同卖家账…

【Python数据结构与算法】递归----上台阶

题目&#xff1a;上台阶 描述 有n级台阶&#xff08;0<n<20&#xff09;&#xff0c;从下面开始走要走到所有台阶上面&#xff0c;每步可以走一级或两级&#xff0c;问有多少种不同的走法。 输入 一个整数n 输出 走法总数 样例输入 4样例输出 5AC代码 def ways(n): …

前端-如何通过docker打包Vue服务成镜像并在本地运行(本地可以通过http://localhost:8080/访问前端服务)

1、下载安装docker&#xff0c;最好在vs code里安装docker的插件。 下载链接&#xff1a;https://www.docker.com/products/docker-desktop &#x1f389; Docker 简介和安装 - Docker 快速入门 - 易文档 (easydoc.net) 2、准备配置文件-dockerfile文件和nginx.conf文件 do…

YOLO:训练自己的样本数据集进行目标检测

作者&#xff1a;CSDN _养乐多_ 本文将介绍如何使用python语言和 ultralytics 库训练自己的数据集&#xff0c;并进行 YOLO 目标检测模型训练和推理的代码。 文章目录 一、样本数据集准备1.1 标注工具1.2 数据集格式1.2.1 图片和标签数据集制作1.2.2 data.yaml制作 二、模型训…

SAP 采购订单审批 Flexible Workflow

目录 1 简介 2 业务数据 1&#xff09;下采购订单&#xff0c;如果订单金额超过 15w 生成 Flexible Workflow 审批 2&#xff09;审批采购订单 - 系统默认主页显示需要审批的采购订单&#xff0c;也可以设置成发邮件提醒 3 后台配置 4 前台主数据定义 1&#xff09;创建…

【初学人工智能原理】【9】深度学习:神奇的DeepLearning

前言 本文教程均来自b站【小白也能听懂的人工智能原理】&#xff0c;感兴趣的可自行到b站观看。 代码及工具箱 本专栏的代码和工具函数已经上传到GitHub&#xff1a;1571859588/xiaobai_AI: 零基础入门人工智能 (github.com)&#xff0c;可以找到对应课程的代码 正文 深度…

14.按钮和多选框

<p>爱好&#xff1a;<input type"checkbox" value"Riding" name"hobby">骑行<input type"checkbox" value"experiment" name"hobby">做实验<input type"checkbox" value"lea…

牛客JS题(十六)生成页码

注释很详细&#xff0c;直接上代码 涉及知识点&#xff1a; 合法性判断JS除法的特点 题干&#xff1a; 我的答案 <!DOCTYPE html> <html><head><meta charsetutf-8></head><body><ul id"ul"></ul><script type&…

Vsftp 源码安装部署(CentOS 8)

概述 运行环境是 CentOS 8.2 ,尝试搭建 ftp 服务&#xff0c;网上找了很多的方式&#xff0c;基本都是 yum 安装&#xff0c;但是因为未连接互联网&#xff0c;因此只能源码安装。 rpm 包下载地址&#xff08;无效&#xff09; RPM resource vsftpd 基本都是针对 CentOS 9 以…

RV1103调用摄像头运行yolov5进行实时检测

目录 前言运行Yolov5模型第一步&#xff1a;修改CMA_SIZE第二步&#xff1a;修改yolov5项目代码第三步&#xff1a;运行程序 前言 买了luckfox pico的rv1103开发板&#xff0c;摄像头是SC3336 3MP Camera (A)摄像头&#xff0c;参考RV1103 Luckfox Pico使用SPI NAND Flash烧录…

数据库备份与恢复和日志管理

一、数据库备份的概述 1、数据库备份的目的&#xff1a;备灾。在生产环境中&#xff0c;数据的安全性非常重要 2、造成数据丢失的原因&#xff1a;程序出错、人为的问题、磁盘故障、自然灾害。 二、备份的分类 从物理和逻辑的角度 1、物理备份&#xff1a; 对数据库操作系…

可视掏耳勺安全吗?独家揭示六大风险弊病!

很多人习惯在洗漱完顺手拿一根棉签掏耳朵&#xff0c;但是棉签的表面直径大且粗糙&#xff0c;不易将耳朵深处的耳垢挖出&#xff0c;耳垢堆积在耳道深处长时间不清理会导致堵塞耳道&#xff0c;引起耳鸣甚至感染。而可视掏耳勺作为一种新型的挖耳工具&#xff0c;它的安全性也…

OpenAI突然上线两件“杀手锏”:势在维持大模型霸主地位

在最近的大模型战争中&#xff0c;OpenAI似乎很难维持霸主地位。虽然没有具体的数据统计&#xff0c;但Claude3.5出现后&#xff0c;只是看网友们的反响&#xff0c;就能感觉到OpenAI订阅用户的流失&#xff1a;既然Claude3.5比GPT-4o好用&#xff0c;为什么我们不去订阅Claude…

html+css 实现遮罩按钮

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享htmlcss 绚丽效果&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 文…

mybatis-plus批量插入优化

mybatis-plus批量插入优化 背景优化新的问题分批插入springboot3整合mybaits-plus 背景 使用的mybatisplus的批量插入方法&#xff1a;saveBatch()&#xff0c;打印 sql 日志发现&#xff0c;底层还是一条条的 insert 语句&#xff0c;这显然是不行的 优化 之前就看到过网上都…

怎么把C盘分成两个盘?让C盘分区更简单,赶快试试!

在日常使用电脑的过程中&#xff0c;有时我们可能希望将C盘分割成两个独立的分区&#xff0c;以便更好地管理文件和数据。这种操作需要谨慎进行&#xff0c;因为错误的分区操作可能导致数据丢失。那么&#xff0c;我们该怎么把C盘分成两个盘呢&#xff1f;下面&#xff0c;我将…

Telegraf 命令行指南:高效监控数据的秘诀

Telegraf 是一个轻量级的服务器监控代理&#xff0c;它支持从数百种数据源收集、处理和发送数据到各种存储库。它由 InfluxData 开发&#xff0c;常用于时间序列数据库 InfluxDB。Telegraf 的灵活性和强大的插件系统使其成为监控基础设施的理想选择。本文将为您提供一个 Telegr…

【解决方案】华普微基于收发芯片系列的LED智能灯控高效解决方案

一、方案概述 LED智能灯是一种集LED照明技术与智能控制技术于一体的现代照明产品。它采用高效节能的LED作为光源&#xff0c;相比传统灯具&#xff0c;具有更低的能耗、更长的使用寿命以及更环保的特性。 智能灯通过内置的智能芯片或连接外部智能设备&#xff08;如智能手机、…