首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >《C++基础:输入输出、缺省参数,函数重载与引用的巧妙》

《C++基础:输入输出、缺省参数,函数重载与引用的巧妙》

作者头像
用户11915063
发布2025-11-20 09:25:44
发布2025-11-20 09:25:44
230
举报

一、C++的输入输出

关键要点:
  • <iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输入、输出对象。
  • std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。
  • std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流。
  • std::endl 是一个函数,流插入输出时,相当于插入一个换行字符加刷新缓冲区。
  • <<是流插入运算符,>>是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)
  • 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的支持自定义类型对象的输入输出。
  • IO流涉及类和对象,运算符重载、继承等很多面向对象的知识,这些知识我们还没有讲解,所以这里我们只能简单认识⼀下C++ IO流的用法。
  • cout/cin/endl等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
  • ⼀般日常练习中我们可以using namespace std,实际项目开发中不建议using namespace std。
  • 这里我们没有包含<stdio.h>,也可以使用printf和scanf,在包含<iostream>间接包含了。vs系列编译器是这样的,其他编译器可能会报错。
举例说明:
代码语言:javascript
复制
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
	int a, b;
	cin >> a >> b;//输入

	cout << a << " " << b << endl;
	cout << a << " " << b << '\n';

	return 0;
}

--用'\n'的效率会更高,除此以外,cout和cin的效率其实也是不如printf和scanf的,但是我们可以通过取消同步流的操作来解决这个问题,代码如下

代码语言:javascript
复制
#include<iostream>
using namespace std;
 
int main()
{
	//取消同步流
	//在io需求比较高的地方,比如需要大量输入的竞赛题中,加上以下3行代码,可以提高效率
	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
}

二、缺省参数

关键要点:
  • 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把缺省参数也叫默认参数)
  • 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值
  • 带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参
  • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
举例说明:

--包含全缺省和半缺省,需要注意的地方,注释都讲的比较清楚

代码语言:javascript
复制
#include<iostream>
using namespace std;

void func(int a = 0)
{
	cout << a << '\n' << '\n';
}

//全缺省
void func1(int a = 10, int b = 20, int c = 30)
{
	cout << "a= " << a << '\n';
	cout << "b= " << b << '\n';
	cout << "c= " << c << '\n' << '\n';
}

//半缺省
//从右至左依次缺省才可以,中间不能跳跃缺省,不能出现 int a=10,int b=20,int c或int a=10,int b,int c=30等这种情况
//如果非要a缺省的话,我们可以把a和c的位置换一下:void func2(int c, int b, int a = 30)
void func2(int a, int b, int c = 30)
{
	cout << "a= " << a << '\n';
	cout << "b= " << b << '\n';
	cout << "c= " << c << '\n' << '\n';
}

int main()
{
	func(1);//1,传参时,使用的是指定的实参
	func();//没有传参会使用缺省值

	//以下4种都可以
	func1(1, 2, 3);
	func1(1, 2);
	func1(1);
	func1();
	//这种不行,不能跳跃传参
	//func1(1, , 3);

	func2(1, 2, 3);
	func2(1, 2);
	return 0;
}

缺省参数在改进数据结构中也使用的比较多,我们这里以顺序表这个结构的几个接口来看看,中间的实现我会比较省略,详细的结构实现可以去看一下博主之前的博客

SeqList.cpp:

SeqList.h:

test.c:

代码语言:javascript
复制
#include<iostream>
#include"SeqList.h"
using namespace std;

int main()
{
	int n;
	cin >> n;
	SL s;
	SLInit(&s, n);//已经知道n的大小
	//如果没有输入操作这里n就是不知道的,就会使用缺省的n=4

	//为啥要定义这么一个n呢
	//比如我要尾插1000个数据,那么不用这个的话,就要不停的扩容,会有消耗
	for (int i = 0; i < n; i++)
	{
		SLPushBack(&s, i);
	}

	//Find
	//原来的find无法实现找顺序表中所有需要找的元素(重复的只会返回第一个)
	//改了之后,通过下述操作可以找到所有的
	//  5 4 6 3 4 7 4
	// // 查找出所有的4
	int x = SLFind(&s, 4);
	while (x != -1)
	{
		x = SLFind(&s, 4, x + 1);
	}
	return 0;
}

三.函数重载

我们知道在C语言中不支持同一作用域中出现同名函数。但是C++是支持的,不过要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调用就表现出了多态的行为,使用更加灵活

三种构成重载的一般情况:

1.参数类型不同(可以自动识别参数类型):

代码语言:javascript
复制
//1.函数参数类型不同
#include<iostream>
using namespace std;
 
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;
}
 
int main()
{
	cout << Add(1, 2) << '\n';
	cout << Add(1.1, 2.2) << '\n';
	//自动识别函数传的参数类型,调用不同的函数;
	return 0;
}

2.参数个数不同:

代码语言:javascript
复制
//2.函数参数个数不同
#include<iostream>
using namespace std;
 
void f1()
{
	cout << "f1()" << '\n';
}
 
void f1(int a)
{
	cout << "f1(int a)" << '\n';
}
 
int main()
{
	f1();
	f1(1);
	//根据传参的个数,自动识别调用对应函数
	return 0;
}

3.参数类型顺序不同(可以自动识别):

代码语言:javascript
复制
//3.函数类型顺序不同--本质上还是对应的函数参数类型不同
#include<iostream>
using namespace std;
 
void f1(double a,int b)
{
	cout << "f1(double a,int b)" << '\n';
}
 
void f1(int a,double b)
{
	cout << "f1(int a,double b)" << '\n';
}
 
int main()
{
	f1(2.3,1);
	f1(1,2.3);
	//根据传参的顺序来调用对应函数
	
	return 0;
}
两种特殊情况:

无法构成重载:(函数的仅仅只是返回类型不同)

代码语言:javascript
复制
//1.无法构成重载
#include<iostream>
using namespace std;
 
void fork()
{
}
 
int fork()
{
	return 1;
}
 
int main()
{
	//调用时无法确定调用那个
	fork();
	int x = fork();
	return 0;
}

E0311:无法重载仅按返回类型区分的函数

可以构成重载,但是调用时会出现歧义:(缺省的情况)

代码语言:javascript
复制
//2.可以构成重载
//但是一些情况下调用存在歧义
#include<iostream>
using namespace std;
 
void f1()
{
	cout << "f()" << endl;
}
 
void f1(int a = 10)
{
	cout << "f(int a)" << endl;
}
 
int main()
{
	f1(1);//这个可以
	f1();//这样调用就不行了,不确定调用那一个
	return 0;
}

四.引用

引用的概念和定义:

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

写法:

类型& 引用别名 = 引用对象

在C++中为了避免引入太多的运算符,会复用C语言的一些符号,比如前面的<<和>>,以及这里的&可以是取地址,引用还可以是按位与运算符,特别容易混淆,大家需要注意区分的角度。

引用的特性:
  • 引用在定义时必须初始化
  • 一个变量可以有多个引用
  • 引用一旦引用⼀个实体,再不能引用其他实体
举例说明:
代码语言:javascript
复制
#include<iostream>
using namespace std;
 
int main()
{
	int i = 1;
	int& j = i;//j是i的别名
 
	//一个变量可以有多个别名,引用。别名也可以有它的别名
	int& k = j;
	k++;//k的变化会影响i和j
 
	cout << &i << '\n';
	cout << &j << '\n';
	cout << &k << '\n';
	//打印发现地址一样
 
	//引用时必须先初始化
	//int& x;//看报错
	//x = i;
 
	// 引用一旦引用一个实体,再不能引用其他实体,引用也不能改变指向
	//所以这里的操作是赋值而不是引用
	int m = 20;
	k = m;
 
	return 0;
}
引用的使用:
  • 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率改变引用对象时同时改变被引用对象
  • 引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
  • 引用返回值的场景相对比较复杂,我们在这里后续会简单讲一下场景,还有一些内容后续在类和对象的学习中会继续深入了解。
  • 引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。
  • 一些主要用C代码实现版本数据结构教材中,使用C++引用替代指针传参,目的是简化程序,避开复杂的指针,但是我们可能当时还没学过引用,导致一头雾水。
举例说明:

例子1:(交换值,在顺序表中的应用)

代码语言:javascript
复制
//指针,引用
//大部分情况下引用都可以替代指针,除了一些特殊情况,比如链表的树的节点的定义只能使用指针
 
#include<iostream>
using namespace std;
 
void swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}
 
void swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}
 
int main()
{
	//swap函数交换值
	int a = 1; int b = 7;
	cout << a << " " << b << '\n';
	swap(&a, &b);//用指针
	cout << a << " " << b << '\n';
	swap(a, b);//用引用
	cout << a << " " << b << '\n';
 
 
	return 0;
}

例子2:(交换指针,在链表中的使用)

代码语言:javascript
复制
//替代二级指针使用
#include<iostream>
using namespace std;
 
void swap(int**x, int** y)
{
	int*tmp = *x;
	*x = *y;
	*y = tmp;
}
 
void swap(int*& x, int*& y)
{
	int*tmp = x;
	x = y;
	y = tmp;
}
 
int main()
{
	//swap函数交换指针
	int a = 1; int b = 7;
	int* pa = &a; int* pb = &b;
	cout <<*pa << " " <<*pb << '\n';
	swap(&pa, &pb);//用指针
	cout << *pa << " " << *pb << '\n';
	swap(pa, pb);//用引用
	cout << *pa << " " << *pb << '\n';
 
 
	return 0;
}

--我们再来看看在链表中的使用,还是省略化的展示

代码语言:javascript
复制
//在链表中的使用
#include<iostream>
using namespace std;
 
typedef struct SListNode
{
	struct SListNode* next;
	int val;
}SLTNode;//, //*PSLTNode;
 
//typedef struct SListNode SLTNode;
//typedef struct SListNode* PSLTNode;
 
 
//以前使用二级指针的实现方法
//void SLTPushBack(SLTNode** pphead, int x)
//{
//	SLTNode* newnode; // = malloc
//
//	if (*pphead == NULL)
//	{
//		*pphead = newnode;
//	}
//	else
//	{
//		// 找到尾结点,newnode链接到尾结点
//	}
//}
 
//void SLTPushBack(PSLTNode& phead, int x)//这个在书上有时候会用
void SLTPushBack(SLTNode*& phead, int x)
{
	SLTNode* newnode = NULL; // = malloc,这里省掉过程
 
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		// 找到尾结点,newnode链接到尾结点
	}
}
 
 
int main()
{
	//用二级指针
	SLTNode* plist = NULL;
	//SLTPushBack(&plist, 1);
	//SLTPushBack(&plist, 2);
	//SLTPushBack(&plist, 3);
	//SLTPushBack(&plist, 4);
 
	//用引用
	//PSLTNode plist = NULL;
	SLTNode* plist = NULL;
	SLTPushBack(plist, 1);
	SLTPushBack(plist, 2);
	SLTPushBack(plist, 3);
	SLTPushBack(plist, 4);
 
	return 0;
}

我们来看一个可能有的教材上会出现,但是很容易把我们看懵的形式:(注意看图片解释)

-在大部分的场景下,引用都可以代替指针使用,但是在链表,树的节点定义位置只能使用指针,引用无法替代。 这是因为:C++的引用无法改变指向,节点一定存在改变指向的场景

传值返回,传引用返回:
传值返回:(主要看错误的地方)
代码语言:javascript
复制
#include<iostream>
using namespace std;
int fun()
{
	int ret = 0;
 
	return ret;
}
 
int main()
{
	int x = fun();//x接受的其实是ret的拷贝值,在fun函数销毁时ret就没了,通过临时变量带出
	//fun() += 1;//所以这里就无法直接修改
	return 0;
}

这里返回接收的是ret吗?--不是,是ret的临时拷贝

传引用返回:(主要看错误的地方)
代码语言:javascript
复制
//传引用返回
#include<iostream>
using namespace std;
 
int& fun()
{
	int ret = 0;
 
	return ret;
}
 
int main()
{
	int&x = fun();
	cout << x << '\n';//这里可能会是随机值
 
	return 0;
}

这里的x可能是0也可能是随机值,为什么? 相当于野指针的访问,比较危险

--我们再来看看一个更奇怪的例子,为啥x后面没有变,但却是y的值,依旧是看错误的地方

代码语言:javascript
复制
#include<iostream>
using namespace std;
int& func1()
{
	int ret = 0;
	// ...
	return ret;
}
 
int& func2()
{
	int y = 456;
	// ...
	return y;
}
 
int main()
{
	int& x = func1();
	cout << x << endl;
	func2();//明明没有修改x,但x的值还是会为y,这是因为fun1函数栈帧销毁后还给了操作系统,但是func2又使用了这块空间
	cout << x << endl;
 
	return 0;
}

为什么会出现这样的现象呢,结合上一个例子可以思考一下:

1.销毁了之后为什么还可以访问?

--因为销毁了并不代表空间没了,内存空间是可以反复使用的,销毁的本质是还给操作系统,跟租房一样,返回的别名就相当于偷偷留了把钥匙,但是这样是不可取的。

2.这里相当于野指针的访问为什么没报错?

--越界不一定报错,越界写可能会报错,在数组中存在越界抽查这个现象。所以我们有时候编译器不会在这里报错的

传引用返回的正确使用:(顺序表为例)

--我们这里的实现还是简略的写一下,中间使用了引用的写法,大家刚好可以看看,注意看注释

SeqList.h:

代码语言:javascript
复制
#pragma once
#include<stdio.h>
#include<stdlib.h>
 
typedef struct SeqList
{
	int* arr;
	int size;
	int capacity;
 
}SL;
 
void SLInit(SL& pls, int n = 4);
void SLPushBack(SL& pls, int x);
int SLFind(SL& pls, int x, int i = 0);
int& SLat(SL& pls, int i);//这里使用引用就又可以读取i位置的值,又可以修改了
void SLModify(SL& pls, int i, int x);//这个并不好用

SeqList.cpp:

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include"SeqList.h"
 
void SLInit(SL& pls,int n)
{
	pls.arr = (int*)malloc(n * sizeof(int));
	pls.size = 0;
	pls.capacity = n;
}
 
void SLPushBack(SL& pls, int x)
{
	//………………
	pls.arr[pls.size++] = x;
}
 
int SLFind(SL& pls, int x,int i)
{
	while (i < pls.size)
	{
		//………………
	}
 
	return -1;
}
 
int& SLat(SL& pls, int i)//这里使用引用就又可以读取i位置的值,又可以修改了
{
	//……
	return pls.arr[i];//这里可以使用引用返回是因为它是结构体中的一个在堆的数据
}
	
void SLModify(SL& pls, int i, int x)//这个并不好用
{
	//……
	pls.arr[i] = x;
}

test.c:

代码语言:javascript
复制
//那我们什么时候可以使用到这个传引用返回呢
//我们拿顺序表这个数据结构为例子,当然栈也可以
 
#include"SeqList.h"
#include<iostream>
using namespace std;
 
int main()
{
	SL s;
	SLInit(s, 10);
	for (size_t i = 0; i < 10; i++)
	{
		SLPushBack(s, i);
	}
 
	for (size_t i = 0; i < 10; i++)
	{
		cout << SLat(s, i) << " ";
	}
	cout << endl;
 
	// 把顺序表第i个位置的值修改为x
	int i = 0;
	int x = 0;
	cin >> i;
	cin >> x;
 
	//SLModify(&s, i, x);//不好用
	SLat(s, i) = x;//用了返回引用这里就可以直接修改赋值了
 
	for (size_t i = 0; i < 10; i++)
	{
		cout << SLat(s, i) << " ";
	}
	cout << endl;
 
	return 0;
}

--这里成功使用了传引用返回,所以是又可以读又可以写的

引用还有一部分知识将会在下篇博客中讲解


往期回顾:

【数据结构初阶】--排序(四):归并排序

【数据结构初阶】--排序(五):计数排序,排序算法复杂度对比和稳定性分析

《C++起源与核心:版本演进+命名空间法》

总结:本篇博客就到此结束了,主要给大家分享了C++的输入输出,参数缺省,重载以及引用的知识点,但是引用的知识点还有一些,博主会在下一篇博客中继续给大家分享,如果文章对你有帮助的话,欢迎评论,点赞,收藏加关注,感谢大家的支持。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-11-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、C++的输入输出
    • 关键要点:
    • 举例说明:
  • 二、缺省参数
    • 关键要点:
    • 举例说明:
  • 三.函数重载
    • 三种构成重载的一般情况:
    • 两种特殊情况:
  • 四.引用
    • 引用的概念和定义:
    • 引用的特性:
      • 举例说明:
    • 引用的使用:
      • 举例说明:
    • 传值返回,传引用返回:
      • 传值返回:(主要看错误的地方)
      • 传引用返回:(主要看错误的地方)
      • 传引用返回的正确使用:(顺序表为例)
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档