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

9.14 c++基础

目录

域的概念:

命名空间域封装函数和结构体

命名空间的嵌套:

std,c++官方库定义的命名空间

using namespace std

缺省参数:

全缺省参数:

半缺省参数:

函数重载:

为什么cout能够识别类型:

如何辨别出函数重载:

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

返回值类型不相同是否构成函数重载:

引用:

引用的作用:



域的概念:

我们在c语言就接触过域,参数的作用域也就是域。

我们之前学习的域分为两部分,局部域和全局域。

这里,我们思考一个问题:当局部域和全局域同时存在时,我们该如何处理呢?

我们写代码进行尝试:

#include<stdio.h>
int a = 0;

int main()
{
	int a = 1;
	printf("%d", a);
}

如图所示的代码中,我们打印的结果是多少?

我们打印的结果为1,证明了当全局域和局部域同时存在时,我们要优先考虑局部域。

那我们有没有一种方法可以访问全局域中的参数呢?

如代码所示:

#include<stdio.h>
int a = 0;

int main()
{
	int a = 1;
	printf("%d\n", a);
	printf("%d\n", ::a);
}

这里的::叫做域作用限定符,当域作用限定符之前没有具体的域的时候,我们访问的就是全局域。

 如图所示,当我们使用域作用限定符时,我们打印的结果就是全局域中变量a的值。

我们思考一个问题:

在同一个域中,能不能有两个同名的参数变量?

#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main()
{
	printf("%d", rand);
}

不能,这是上节课我们写的代码,我们进行编译:

 错误的原因是重定义:

原因是这样:当我们运行代码时的预处理阶段,我们就会把头文件包含的内容进行展开,在头文件#include<stdlib.h>中,我们已经包含了一个叫做产生随机数的函数:

 而我们在全局域中又创建了一个整型参数rand,这两个就会放生冲突。

所以:在同一个域中不能有两个同名的参数。

那假设我们要访问的是头文件中的rand,我们该如何操作:

#include<stdio.h>
#include<stdlib.h>
namespace bit
{
	int rand = 10;
}

int main()
{
	printf("%d", rand);
}

namespace bit表示命名空间域,表示我们再创建一个域,这个域在正常情况下不会被直接访问,我们把我们的整型参数rand放在bit域中,我们再进行编译:

这时候,我们打印的就是预处理阶段,头文件展开中的rand函数,因为打印函数时,默认打印的是函数的地址。

我们也可以用地址的16进位制显示一下: 

#include<stdio.h>
#include<stdlib.h>
namespace bit
{
	int rand = 10;
}

int main()
{
	printf("%p", rand);
}

这时候,我们再思考一个问题:在该命名空间域定义的变量是属于全局变量还是局部变量。

我们可以这样分析:首先,我们常使用的有三个空间,三个空间分别是:栈,静态区,堆

其中:栈帧是在函数调用时产生的,静态区是用来存储全局变量的,堆是动态申请开辟的空间产生的。

因为局部变量全部是存储在函数中的,所以局部变量也是在栈帧的范畴中,而rand并不是在函数中的,所以不是局部变量,而是全局变量。

总结:命名空间域不影响变量的生命周期,只是限定域,编译查找规则。

#include<stdio.h>
#include<stdlib.h>
namespace bit
{
	int rand = 10;
}

void func()
{
	printf("%d\n", rand);
}
int main()
{
	func();
	printf("%d", rand);
}

我们打印的结果是多少?

原因是我们在查找是时,默认现在局部域中找,没有找到rand,再去全局域中找,命名空间域限定了域,所以我们只能在当预处理时,头文件展开时才能访问到#include<stdlib.h>,我们访问的是rand函数,打印的是rand函数的地址。

我们可以使用域操作限定符来访问域bit中的rand

#include<stdio.h>
#include<stdlib.h>
namespace bit
{
	int rand = 10;
}

void func()
{
	printf("%d\n", bit::rand);
}
int main()
{
	func();
	printf("%d", bit::rand);
}

我们的默认查找规则是先在局部找,再在全局找。

但是当我们使用域操作限定符时,我们默认会在域bit中找。

命名空间域封装函数和结构体:

namespace bit
{
	int rand = 10;
	int x = 1;
	int Add(int left, int rigt)
	{
		return left + right;
	}
	struct Node
	{
		struct Node*next;
		int val;
	};
}

我们思考:用命名空间域封装函数Add和结构体Node和不加命名空间有什么区别。

答:区别在于当用命名空间封装时,当我们没有使用域操作限定符时,我们是不能访问对应的函数和结构体的。

#include<stdio.h>
#include<stdlib.h>
namespace bit
{
	int rand = 10;
	int x = 1;
	int Add(int left, int right)
	{
		return left + right;
	}
	struct Node
	{
		struct Node*next;
		int val;
	};
}
int main()
{
	printf("%d\n", Add(1, 2));
	return 0;
}

当我们进行运行时:

 我们找不到函数Add的话,就必须得使用域操作限定符:

int main()
{
	printf("%d\n", bit::Add(1, 2));
	return 0;
}

进行编译:

当我们需要用结构体时,我们需要把域操作限定符放到struct的后面:

int main()
{
	printf("%d\n", bit::Add(1, 2));
	struct bit::Node node;
	return 0;
}

为什么要把函数和结构体封装呢?因为我们可以使用一种更方便的写法:

#include<stdio.h>
#include<stdlib.h>
namespace bit
{
	int rand = 10;
	int x = 1;
	int Add(int left, int right)
	{
		return left + right;
	}
	struct Node
	{
		struct Node*next;
		int val;
	};
}
namespace byte
{
	int Add(int left, int right)
	{
		return left * 10 + right * 10;
	}
	struct Node
	{
		struct Node*next;
		struct Node*prev;
		int val;
	};
}

这里我们的bit是一个域,我们的byte也是一个域,我们把byte域中的Add函数和结构体进行修改,并使两个域中函数和结构体的名字是相同的,假如在c语言中,我们是无法进行区分的,但是我们使用了两个域进行封装时,我们是可以进行区分的:

int main()
{
	printf("%d\n", bit::Add(1, 2));
	printf("%d\n", byte::Add(1, 2));
	struct bit::Node node;
	struct byte::Node node0;
	return 0;
}

我们进行编译:

 用命名空间域去封装函数很好的避免了命名冲突。

命名空间的嵌套:

namespace N1
{
	int a;
	int b;
	int Add(int left, int right)
	{
		return left + right;
	}
	namespace N2
	{
		int c;
		int d;
		int Sub(int left, int right)
		{
			return left + right;
		}
	}
}

命名空间域N1嵌套着N2,要注意,我们使用N1和N2时,对应的操作限定符是不同的。

int main()
{
	N1::a = 1;
	N1::N2::c = 1;
}

我们如何找到c?

我们要现在域N1中找N2,找不到N2就报错,再在N2中找c,找不到c就报错。

std,c++官方库定义的命名空间

我们再添加两个头文件,一个头文件我们简写一个栈,一个我们简写一个队列:

 

 我们在两个空间中使用了相同的命名空间,这两个命名空间是可以合并的。

#include<stdlib.h>
#include"Stack.h"
#include"Queue.h"

int main()
{
	bit::StackPush(NULL, 1);
	bit::QueuePush(NULL, 1);
	return 0;
}

 合并之后,我们都可以通过域操作限定符来使用命名空间内的函数。

总结:同一个工程中允许有多个相同名称的命名空间,编译器最后会合成同一个命名空间。

那么,同一个文件相同名称的命名空间可以被合并吗?

答:可以,看我们之前写的这串代码:

这两个头文件在预处理时会展开,展开会有两个相同名称的命名空间,这里就发生了合并,这里其实就等价于同一个文件相同名称的命名空间的合并。

using namespace std

我们在打印函数时,会遇到这种情况:

#include<iostream>
int main()
{
	std::cout << "hello bit" << std::endl;
	std::cout << "hello bit" << std::endl;
	std::cout << "hello bit" << std::endl;
}

 假如我们要多次打印时,我们每一次打印都需要写std::count  std::endl,有没有什么写法可以避免这种问题?

我们可以使用using namespace std

using namespcae std的意思是展开命名空间std的内容,表示我们可以使用std官方库的函数参数等等。

#include<iostream>
using namespace std;
int main()
{
	cout << "hello bit" << endl;
	cout << "hello bit" << endl;
	cout << "hello bit" << endl;
}

我们进行编译:

但是这里其实就存在一些隐患了,因为我们把std库都展开了,std库是非常庞大的,难免我们会定义出和std库中同名的参数或函数:

 例如:

#include<iostream>
using namespace std;
int cout = 10;
int main()
{
	cout << "hello bit" << endl;
	cout << "hello bit" << endl;
	cout << "hello bit" << endl;
}

我们进行编译:

 错误的原因是我们定义的cout与std库中的cout产生了冲突。

我们可以采取封装的办法来处理这些问题:

#include<iostream>
using namespace std;
int cout = 10;
int main()
{
	std::cout << "hello bit" << endl;
	std::cout << "hello bit" << endl;
	std::cout << "hello bit" << endl;
}

这样就表示我们要找的是std库中的cout。

我们进行编译:

当然,我们还可以这样写:

#include<iostream>
using std::cout;

int main()
{
	cout << "hello bit" << std::endl;
	cout << "hello bit" << std::endl;
	cout << "hello bit" << std::endl;
}

我们进行编译:

cout << "hello bit" << std::endl;

 我们对这串代码进行分析:

cout表示的是console out,意思就是控制台输出,就和c语言的printf类似

cin就是console in,表示的是控制台输入,和c语言的scanf函数类似。

endl就相当与"\n"的意思,就是换行。

这里的<<表示的是流插入运算符

cin使用的>>表示的是流提取运算符。

这里的cin和cout相较于c语言中的scanf和printf有能够自动识别类型的优点:

#include<iostream>
using namespace std;

int main()
{
	int a;
	double b;
	char c;
	cin >> a;
	cin >> b >> c;
	cout << a << endl;
	cout << b << c << endl;
	return 0;
}

我们进行编译:

我们输入a为3,b为3.33,c为a

 

 我们可以发现,cin和cout可以自动识别类型。

缺省参数:

using namespace std;
void Func(int a = 0)
{
	cout << a << endl;
}
int main()
{
	Func();
}

如图所示,我们看Func函数,我们可以发现Func函数的参数是int a=0,这是默认给的值。

缺省参数的意思:

1:假如我们调用Func函数没有传递参数,那么我们的参数就默认是为这里设置的0

2:当我们调用Func函数传递了参数,那我们的参数就为这个参数。

如图所示代码,我们的Func函数表示打印参数a,当我们不传参数时,我们的参数就默认为a,打印的结果就为0.

 当我们传递参数时,如图所示:

#include<iostream>
using namespace std;
void Func(int a = 0)
{
	cout << a << endl;
}
int main()
{
	Func();
	Func(10);
}

我们打印的结果应该为10.

全缺省参数:

#include<iostream>
using namespace std;
void Func(int a = 0, int b = 10,int c=100)
{
	cout <<"a=" <<a<< endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
}

如图所示,我们所有的参数全部都是缺省的,这时候,我们调用函数不传参时:

#include<iostream>
using namespace std;
void Func(int a = 0, int b = 10,int c=100)
{
	cout <<"a=" <<a<< endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
}
int main()
{
	Func();
}

我们调用的结果应该就是:0,10,100.

我们传递参数时,需要从左向右传,并且不能有中断,例如,我们可以传参数a,b

#include<iostream>
using namespace std;
void Func(int a = 0, int b = 10,int c=100)
{
	cout <<"a=" <<a<< endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
}
int main()
{
	Func(10,20);
}

这样,c就应该是默认值100,a和b就应该是参数值10和20.

但是我们不能传递参数b,c或a,c

注意:我们传递的第一个参数对应的就是函数的第一个参数,第二个参数对应的就是函数的第二个参数:

半缺省参数:

#include<iostream>
using namespace std;
void Func(int a, int b = 10,int c=100)
{
	cout <<"a=" <<a<< endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
}

如图所示,参数a没有缺省,其他参数缺省了,这种就叫做半缺省参数。

注意:半缺省参数必须保证参数从右往左是连续的:

例如:这样的半缺省就是错误的:

 或者是这样:

总结:缺省参数的参数列表必须是从右到左连续的,不能有中断。

调用缺省参数的函数的参数必须是从左到右连续的,不能有中断。

缺省参数不能声明和定义同时出现:

例如:

 

 缺省参数不能声明和定义同时出现就是为了防止这种情况,也就是缺省参数的声明和定义并不一致的问题,所以我们不能在定义中使用缺省参数,只能在声明中使用缺省参数。

函数重载:

重载的意思就是“一词多义”

我们写代码进行演示:

#include<iostream>
using namespace std;
int Add(int x, int y)
{
	cout << x + y << endl;
	return x+ y;
}
double Add(double x, double y)
{
	cout << x + y << endl;
	return x + y;
}

这两个就是函数重载。

函数重载:函数名相同,参数不同(个数,类型,顺序)。

int Add(int x, int y)
{
	cout << x + y << endl;
	return x+ y;
}
double Add(double x, double y)
{
	cout << x + y << endl;
	return x + y;
}
int main()
{
	int a = 10;
	int b = 12;
	Add(a, b);
	double x = 2.22;
	double y = 3.33;
	Add(x, y);
}

这两个函数本质上是两个不同的函数,但是他们的函数名却相同。

函数重载在交换函数中的使用:

void Swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void Swap(char*a, char*b)
{
	char tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 1, b = 1;
	int c = 2.2, d = 3.3;
	Swap(&a, &b);
	Swap(&c, &d);
}

为什么cout能够识别类型:

cout << a;
	cout << c;

本质上是cout在接收a的时候,调用了函数,这个函数其实也是函数重载,例如我们在接收a和接收c的时候,调用了不同的函数:

cout << a;
	//ostream& operator<<(int val)
	
	cout << c;
	//ostream& operator<<(double val)

这些不同的函数用来接收我们传递来的不同的参数,进而实现自动识别类型的形式。

如何辨别出函数重载:

1:参数个数不同:

void f()
{
	cout << "f()"<<endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}
int main()
{
	f();
	f(1);
	return 0;
}

我们进行编译:

2:参数的类型不同:

void Swap(int *a, int *b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
void Swap(char*a, char*b)
{
	char tmp = *a;
	*a = *b;
	*b = tmp;
}

例如我们的Swap函数就是类型不同。

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()
{
	f(1, 'a');
	f('a', 1);
	return 0;
}

我们尝试把缺省参数和函数重载结合起来:

void f()
{
	cout << "f()" << endl;
}
void f(int a = 0, char c = 'a')
{
	cout << "f(int a=0,char c='a')" << endl;
}
int main()
{
	return 0;
}

如图所示:上面的两个函数是函数重载吗?

答:是,因为他们的函数名相同,参数类型不同。

我们进行编译:

但是当我们进行调用时:

void f()
{
	cout << "f()" << endl;
}
void f(int a = 0, char c = 'a')
{
	cout << "f(int a=0,char c='a')" << endl;
}
int main()
{
	f();
	return 0;
}

 我们进行编译:

 错误的原因是这样:因为有缺省参数的存在,所以我们无法区分我们调用的是函数f()还是f(int a=0,char c='a')。造成二义性。

所以,我们不要同时使用缺省参数和函数重载。

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

答:我们之前学习的时候,学到过符号表,符号表中保存的是函数名及其地址,在c语言中,符号表中是这样写的:

在c++中,对于函数重载,符号表中是这样写的。

 在c++中,对应的函数名是由函数名修饰规则决定的。
函数名修饰规则的主题就是:参数不同,其函数对应的函数名就不同。

既然函数名不同,那么对应的地址就不同。

返回值类型不相同是否构成函数重载:

int f(int a, int b)
{
	cout << "int f(int a,int b)" << endl;
	return 0;
}
char f(int a, int b)
{
	cout << "char f(int a,int b)" << endl;
	return 'a';
}

 我们逐步进行分析:因为函数命名规则是根据函数的参数决定的,因为他们的函数的参数是相同的,所以不是函数重载。

那么,假如我们把函数的返回值类型也作为函数命名规则的一部分,那我们是否构成函数重载?

答:不构成,我们可以这样理解,返回值是不能用来区分函数的,例如,当我们在这里调用f(a,b)和f(a,b),我们是不清楚具体调用的是哪个函数的,所以产生歧义,导致二义性。

引用:

int a = 10;
	int &ra = a;

引用就是取别名的意思,所以这里对应的图像就是这样:

ra和a对应的地址是相同的,换言之:ra就是a,a就是ra,相当于拷贝了一个a。

 所以,对a的处理也会对ra造成影响:

引用的作用:

我们举一个例子:

void Swap(int *a, int *b)
{
	int m = *b;
	*a = *b;
	*a = m;
}

我们从前写Swap函数就是这样写的,因为我们的形参的改变并不影响实参,所以我们要使用指针的方法。

但是现在我们可以这样写。

void Swap(int&a, int &b)
{
	int m = b;
	b = a;
	a = m;
}

因为我们的形参就是实参的引用,换而言之,形参和实参指向的是同一个位置,所以我们修改形参也会使实参发生改变。

void Swap(int&a, int &b)
{
	int m = b;
	b = a;
	a = m;
}
int main()
{
	int c = 10;
	int d = 20;
	Swap(c, d);
}

如图所示:

我们从前写的单链表:

typedef struct ListNode
{
	struct ListNode*next;
	int val;
};
void SlistPushBack(struct ListNode**phead, int x)
{}

单链表进行尾插时,我们要传结构体的二级指针,因为我们要修改1级指针的值。

那我们现在就可以这样写了:

typedef struct ListNode
{
	struct ListNode*next;
	int val;
};
void SlistPushBack(struct ListNode*&phead, int x)
{}

相关文章:

  • python创建智能问答机器人
  • MyBatis的简介和核心的组件(映射器、执行器、SqlSession及其工厂)
  • 《计算几何》学习笔记
  • 干货分享:Docker 容器应用示例
  • 闭包的定义,原理,应用场景,优点,缺点
  • js控制台输出佛祖保佑图形图案/彩色图形图案实例代码
  • 黑客进行攻击中最重要的环节“信息收集”
  • Vue(6)
  • JSON 数据类型转换工具
  • CSP-J 2019 入门级 第一轮 第17题
  • 小程序map组件一——使用腾讯地图个性化地图组件、腾讯云可视化大屏展示
  • 【项目实战】高并发内存池
  • go语言|数据结构:二叉树可视化(svg树形图改进版)
  • JVM - 垃圾回收方式性能研究
  • flink笔记2(Flink 快速上手)
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • extjs4学习之配置
  • If…else
  • JavaScript-Array类型
  • Java多态
  • java中的hashCode
  • js递归,无限分级树形折叠菜单
  • Linux链接文件
  • Netty+SpringBoot+FastDFS+Html5实现聊天App(六)
  • redis学习笔记(三):列表、集合、有序集合
  • 从0到1:PostCSS 插件开发最佳实践
  • 电商搜索引擎的架构设计和性能优化
  • 解决iview多表头动态更改列元素发生的错误
  • 前端 CSS : 5# 纯 CSS 实现24小时超市
  • 如何编写一个可升级的智能合约
  • 使用 Docker 部署 Spring Boot项目
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • 用 Swift 编写面向协议的视图
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • ​中南建设2022年半年报“韧”字当头,经营性现金流持续为正​
  • (1/2)敏捷实践指南 Agile Practice Guide ([美] Project Management institute 著)
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (源码版)2024美国大学生数学建模E题财产保险的可持续模型详解思路+具体代码季节性时序预测SARIMA天气预测建模
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转)eclipse内存溢出设置 -Xms212m -Xmx804m -XX:PermSize=250M -XX:MaxPermSize=356m
  • (转)Java socket中关闭IO流后,发生什么事?(以关闭输出流为例) .
  • ./configure,make,make install的作用
  • .net 4.0 A potentially dangerous Request.Form value was detected from the client 的解决方案
  • .Net Core webapi RestFul 统一接口数据返回格式
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .net core 源码_ASP.NET Core之Identity源码学习
  • .NET(C#) Internals: as a developer, .net framework in my eyes
  • .net6Api后台+uniapp导出Excel
  • [ vulhub漏洞复现篇 ] ThinkPHP 5.0.23-Rce
  • [.net 面向对象程序设计进阶] (19) 异步(Asynchronous) 使用异步创建快速响应和可伸缩性的应用程序...
  • [AX]AX2012 AIF(四):文档服务应用实例
  • [BZOJ] 3262: 陌上花开
  • [C#]扩展方法
  • [Flutter]设置应用包名、名称、版本号、最低支持版本、Icon、启动页以及环境判断、平台判断和打包
  • [hdu 1711] Number Sequence [kmp]