【C++程序员的自我修炼】基础语法篇(一)

心中若有桃花源

何处不是水云间


目录

命名空间

💞命名空间的定义

💞 命名空间的使用

输入输出流

 缺省参数

函数的引用

引用的定义💞

引用的表示💞

 引用的特性💞

 常量引用💞

引用的使用场景 

做参数

做返回值

 ⭐传值、传引用效率比较

 ⭐引用和指针的区别

命名空间

✨在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。

举个栗子:我想输出全局变量 rand 的值

#include<stdlib.h>
#include<stdio.h>

int rand = 1;

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

我们发现 rand 重定义了,因为 #include<stdio.h> 内有以 rand 命名的函数,如果是以前我们为了解决这个问题还要憋屈的去改变量名,而现在 C++ 中的 命名空间 就可以很好的解决问题。

使用命名空间的目的对标识符的名称进行本地化 以避免命名冲突或名字污染

💞命名空间的定义

namespace是命名空间的关键字,Df是命名空间的名字,一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
namespace Df
{
	int rand = 10;
}
⭐命名空间可以嵌套(函数、结构体、另一个命名空间等)
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;
		}
	}
}
⭐同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
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;
		}
	}
}

namespace N1
{
	int Mul(int left, int right)
	{
		return left * right;
	}
}

💞 命名空间的使用

✨<1>加命名空间名称及作用域限定符,:: 是域限定符表示在该命名空间进行访问
namespace Df
{
	int a = 0;
}
int main()
{
	printf("a = %d\n",Df::a);
	system("pause");
}

✨<2>使用 using 将命名空间中某个成员引入
namespace Df
{
	int b = 1;
}
using Df::b;
int main()
{
	printf("b = %d\n", b);
	system("pause");
}

✨<3>使用 using namespace 命名空间名称对该空间进行展开
namespace Df
{
	int a = 0;
	int b = 1;
}
using namespace Df;
int main()
{
	printf("a = %d\n", a);
	printf("b = %d\n", b);
	system("pause");
}

 日常操作🔥

c++库为了防止命名冲突,就把库里面的东西都定义在一个 std 的命名空间里,所以要使用c++库中的函数方法与上述三种方法一样

<1>加命名空间名称及作用域限定符

#include<iostream>
int main()
{
	std::cout << "Hello world !" << std::endl;
	return 0;
}

 <2>使用 using 将命名空间中某个成员引入

using std::cout;
using std::endl;
cout << "Hello world !" << endl;

 <3>对全部命名空间进行展

using namespace std;
cout << "Hello world !" << endl;

一些小细节💥

<1>编译默认查找顺序:当前局部域、全局域、命名空间中查找

举个栗子

如果把对变量的查找比作在地里采菜,那么命名空间就是一个访问权限问题

局部域自己家的地
全局域外面的野地
命名空间隔壁王叔叔家的地
假如某天家里缺辣椒了,自家地里没有,那就要去外面的野地去查找,若还是没有就看看隔壁王叔叔家的地有没有(如果有也不能随便采摘,因为没有经过王叔叔的同意),有权限后才可以采摘(域限定符进行申请访问)。

<2>对全部命名空间进行展开 using namespace std (大型项目不宜使用,避免命名冲突)


输入输出流

cout 标准输出流 和 cin 标准输入流,包含于 #include<iostream> 头文件中

🌤️ >> 是流插入与 cin 搭配使用,<<是流输出与 cout 搭配使用
🌤️ 输入输出流可以自动识别变量类型不需要像 printf/scanf 输入输出时那样,需要手动控制格式
🌤️ cout cin 是全局的流对象,endl 表示换行输出
#include<iostream>
using std::cout;
using std::cin;
using std::endl;
int main()
{
	int a;
	double b;
	cin >> a >> b;
	cout << a << b << endl;
	return 0;
}


 缺省参数

缺省参数是声明或定义函数时为函数的参数指定一个缺省值

在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参

#include<iostream>
using std::cout;
using std::endl;
void Fun(int a = 10)
{
	cout << a << endl;
}

int main(void)
{
	Fun();//没有指定实参则采用缺省值
	Fun(314);
	return  0;
}

缺省参数的类型

🌤️全缺省:形参都是表达式
#include<iostream>
using std::cout;
using std::endl;
void Fun(int a = 10,int b = 20,int c = 30)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main(void)
{
	Fun(1);
	return  0;
}

注意:

<1>缺省参数不是要全部传完,没有指定实参采用该形参的缺省值

<2>缺省值必须从左往右依次传,不可以间隔

🌤️半缺省:形参部分没有表达式
#include<iostream>
using std::cout;
using std::endl;
void Fun(int a,int b = 20,int c = 30)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main(void)
{
	Fun(1);
	return  0;
}

注意:

<1>半缺省参数必须从右往左依次来给出,不能间隔着给

void Fun(int a = 10,int b = 20,int c)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}
int main(void)
{
	Fun(1);
	return  0;
}


因为第三个形参 c 不是表达式,没有缺省值,而我们实参传的数值是传给 a 的所以报错

<2>缺省参数不能在函数声明和定义中同时出现

#include<iostream>
using std::cout;
using std::endl;
void Fun(int a = 10, int b = 20, int c = 30);

int main()
{
	Fun(1);
	return  0;
}

void Fun(int a = 10, int b = 20, int c = 30)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}


函数的引用

引用的定义💞

引用不是新定义一个变量,而是给已存在变量取了一个别名

编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间

引用的表示💞

🌤️类型& 引用变量名(对象名) = 引用实体
#include<iostream>
using std::cout;
using std::endl;
int main()
{
	int a = 10;
	int& b = a;
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "&a = " << &a << endl;
	cout << "&b = " << &b << endl;
	return  0;
}

 因为 b 是给 a 取的别名,我们可以看到 ab以及地址都是一样的

比如水浒传的李逵,你叫黑旋风和铁牛他都会应,这就是引用(取别名)

注意💥

引用类型必须和引用实体是同种类型的

 引用的特性💞

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

<2>一个变量可以有多个引用

#include<iostream>
using std::cout;
using std::endl;
int main()
{
	int a = 10;
	int& b = a;
	int& c = b;
	cout << a << endl << b << endl << c;
	return  0;
}

<3>引用一旦引用一个实体,再不能引用其他实体

 

🌤️引用在定义时必须初始化
🌤️一个变量可以有多个引用
🌤️引用一旦引用一个实体,再不能引用其他实体

 常量引用💞

	const int a = 10;
	//int& ra = a;          //该语句编译时会出错,a为常量
	const int& ra = a;

	double b = 3.14;
	//int& rb = b;          //该语句编译时会出错,类型不同
	double& rb = b;

	int c = 10;
	const int& rc = c;   

加了 const 该变量是不能修改的,即成了一种常量,可以理解为只读型

而没加 const 的可理解为可读可写型

const int a = 10;
int& ra = a;

对常量a进行引用,那么我就可以通过引用ra去修改a的值,权限放大了所以是不行的

int c = 10;

const int& rc = c;  

对变量c进行引用,并将c置为不可修改的常量,权限的缩小所以是可行

const int a = 10;
const int& ra = a;

都是const加以修饰的同类型变量,权限的平移是可行的

总结

<1>权限只能缩小,不能放大,放大就会报错

<2>权限放大和缩小只针对引用和指针

<3>使用引用传参,函数内不改变参数,尽量使用 const 引用传参

引用的使用场景 

做参数

💞<1>还记得我们之前写过的两数交换的代码吗?我们之前传的是指针,我们可以用引用代替

#include<iostream>
using std::cout;
using std::endl;

void Swap(int& n, int& m)
{
	int tmp = n;
	n = m;
	m = tmp;
}
int main()
{
	int a = 10, b = 20;
	Swap(a, b);
	cout << a << endl << b << endl;
	return  0;
}

 💞<2>还记得我们之前写过链表时传的二级指针吗?今非昔比,我们来干爆他

前期回顾:单链表

typedef int SListDataType;

typedef struct SList
{
	SListDataType data;
	struct SList* next;
}SL;

void SListFront(SL** head, SListDataType x)
{
	...
}

这是我们的第一种写法,当初为啥要传二级指针呢?

因为我们没有定义哨兵位(头节点)而我们又想改变我们的首节点,假设我们传单指针SL*,SL*是结构体指针类型,这里是形参(形参是实参的拷贝,出了作用域就销毁),而我们要想改变首节点则必须传地址,所以我们要传二级指针

当然这是我们的一种写法,用引用该如何写呢?

void SListFront(SL*& head, SListDataType x)
{
	...
}

如果你觉得别扭可以将结构体指针的声明放在 typedef 中像这样

typedef int SListDataType;

typedef struct SList
{
	SListDataType data;
	struct SList* next;
}SL*;

void SListFront(SL& head, SListDataType x)
{
	...
}

这样是不是好看多了!!!

做返回值

错误示范)·

#include<iostream>
using namespace std;
int& Count()
{
	int n = 10;
	return n;
}
int main()
{
	int& ret = Count();
	cout << "ret = " << ret << endl;
	cout << "ret = " << ret << endl;
	return 0;
}

 让我们来分析分析,以上打印的值都是 10 吗?

我们发现我们打印的第二个 ret 竟然是随机值,这是为什么呢?

因为局部变量存储在系统的栈区,出了定义域就会销毁

🌤️第一次打印原值是因为编译器在释放时会进行一次保留

Count 函数并不是直接返回 n 的

因为 Count 函数在调用结束后会销毁它所在的栈帧,连同 n 会一起销毁,所以编译器会先保存 n的值到一个寄存器中,再销毁栈帧,然后返回寄存器的值给 ret

🌤️第二次出现乱码是因为赋值后寄存器的空间被编译器销毁

当我们用上面的代码,返回的是 n 的引用时,这就不安全了。因为返回的是 n 的引用,不会创建临时空间给 n,而是直接返回 n 。 但是返回之后 n 所在的函数栈帧会被销毁,所以连同 n 一起销毁了,但是此时 ret n 这块已经不属于自己的空间的拷贝,所以 ret 是违法

那我们想二次调用 n 该怎么办呢?只要不销毁 n 就可以了,我们可以给 n 开辟静态空间:

正确写法

#include<iostream>
using namespace std;
int& Count()
{
	static int n = 10;
	return n;
}
int main()
{
	int& ret = Count();
	cout << "ret = " << ret << endl;
	cout << "ret = " << ret << endl;
	return 0;
}

 

此时 n 是被 static 修饰过的变量,可以用引用进行返回了,因为 n 是在静态区开辟的空间在内存的堆区,而函数是在栈区开辟的空间,所以不会被销毁。其次因为在堆区返回的时候就不需要借助寄存器的临时拷贝了。

局部变量用 static 修饰,出作用域后不销毁,可以传引用返回

没有用 static 修饰的局部变量,出了作用域会被销毁栈,必须用传值返回

引用作为函数的返回值时,必须在定义函数时在函数名前&

用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本(寄存器)

我们在举一个栗子~

#include<iostream>
using namespace std;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

这里为什么输出的是 7 呢?

 

就像我们学指针时遇到的野指针,在我们 free 掉不用的空间后没有将指针置为NULL,此时该指针还指向这块空间,但是值却是随机的

 

因为 c 是在栈区在第一次调用Add时,ret 为 3,Add函数的栈桢销毁,在第二次调用时,Add函数的栈桢是相同的,c 的位置覆盖为 7,再次访问 ret 此时就为 7,因此这里使用是不安全的

总结💥

如果函数返回时,出了函数作用域,如果返回对象还在,则可以使用引用返回,如果已经还给系统了,则必须使用传值返回


 ⭐传值、传引用效率比较

#include <time.h>
#include<iostream>
using namespace std;
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "TestFunc1 time:" << end1 - begin1 << endl;
	cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();
	return 0;
}

 我们发现:引用作为返回值类型大大提高了效率

原因:以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

 ⭐引用和指针的区别

引用在语法概念上引用就是一个别名,没有独立空间,指针在底层实现上实际是有空间的

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	//指针存储a的地址
	int* pa = &a;
	//b是a的引用
	int& b = a;
	return 0;
}

 没有NULL引用,但有NULL指针

#include<iostream>
using namespace std;
int main()
{
	int* a = NULL;
	int& b = a;
	return 0;
}

 

 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数

#include<iostream>
using namespace std;
int main()
{
	double a = 10;
	double* b = &a;  //指针取地址
	cout << sizeof(b) << endl;
	double& c = a;   //引用
	cout << sizeof(c) << endl;
	return 0;
}

 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

#include<iostream>
using namespace std;
int main()
{
	double a = 10;
	double* b = &a; 
	cout << b << endl;
	b++;
	cout << b << endl;
	double& c = a;   
	cout << c << endl;
	c++;
	cout << c << endl;
	return 0;
}

总结 💥

🌤️引用在语法概念上引用就是一个别名,没有独立空间,指针在底层实现上实际是有空间的
🌤️引用在定义时必须初始化,指针没有要求
🌤️引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体
🌤️没有NULL引用,但有NULL指针
🌤️在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数
🌤️引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
🌤️有多级指针,但是没有多级引用
🌤️访问实体方式不同,指针需要显式解引用,引用编译器自己处理
🌤️引用指针使用起来相对更安全

 

先介绍到这里啦~

有不对的地方请指出💞

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

蓝桥杯基础练习汇总详细解析(一)——数列排序、十六进制转八进制、十六进制转十进制、十进制转十六进制、特殊回文数(代码实现、解题思路、Python)

试题 基础练习 数列排序 资源限制 内存限制&#xff1a;512.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s 问题描述 给定一个长度为n的数列&#xff0c;将这个数列按从小到大的顺序排列。1<n<200 输入格式 第…

ATFX汇市:欧元区的2月M1增速为-7.7%,潜在通胀下修,欧元币值受冲击

ATFX汇市&#xff1a;衡量经济体的潜在通胀指标&#xff0c;除了CPI数据、失业率数据外&#xff0c;还有M1、M3数据。昨日&#xff0c;欧洲央行公布了2月份欧元区货币发展报告&#xff0c;其中提到&#xff1a;广义货币总量M3的年增长率从1月份的0.1%上升到2024年2月的0.4%&…

MT9256 Android 智能电视解决方案

一、方案描述 智能电视&#xff0c;是基于Internet应用技术&#xff0c;具备开放式操作系统与芯片&#xff0c;拥有开放式应用平台&#xff0c;可实现双向人机交互功能&#xff0c;集影音、娱乐、数据等多种功能于一体&#xff0c;以满足用户多样化和个性化需求的电视产品。有…

JavaScript高级 —— 学习(一)

目录 一、作用域 &#xff08;一&#xff09;局部作用域 1.函数作用域 2.块作用域 &#xff08;二&#xff09;全局作用域 二、垃圾回收机制 GC &#xff08;一&#xff09;生命周期 1.内存分配 2.内存使用 3.内存回收 4.特殊情况——内存泄漏&#xff1a; 注意&…

【问题分析】InputDispatcher无焦点窗口ANR问题【Android 14】

1 问题描述 Monkey跑出的无焦点窗口的ANR问题。 特点&#xff1a; 1&#xff09;、上层WMS有焦点窗口&#xff0c;为Launcher。 2&#xff09;、native层InputDispacher无焦点窗口&#xff0c;上层为”recents_animation_input_consumer“请求了焦点&#xff0c;但是”rece…

亚信安慧AntDB引领优质解决方案

亚信安慧AntDB数据库在运营商自主可控替换项目中的成功应用&#xff0c;具有极其重要的意义。该数据库的落地&#xff0c;不仅为这一项目注入了强大的支持力量&#xff0c;还在更大程度上提升了整体的运营效能。作为一种高效可靠的数据库解决方案&#xff0c;AntDB引入了先进的…

C++从入门到精通——函数重载

函数重载 前言一、函数重载概念二、函数重载的分类参数类型不同的函数重载参数个数不同的函数重载参数类型顺序不同的函数重载 三、函数重载的具体代码展示main.cpp 四、为什么为什么C支持函数重载&#xff0c;而C语言不支持函数重载呢 前言 函数重载是指在同一个作用域内&…

vue实现文字一个字一个字的显示(开箱即用)

图示&#xff1a; 核心代码 Vue.prototype.$showHtml function (str, haveCallback null) {let timeFlag let abcStr for (let i 0; i < str.length; i) {(function (i) {timeFlag setTimeout(function () {abcStr str[i]haveCallback(abcStr)if ((i 1) str.length…

SI24R2E:智能电子学生卡2.4GHz考勤方案

今年年初教育部发布的《关于加强中小学生手机管理工作的通知》中提出&#xff0c;学生手机有限带入校园&#xff0c;原则上不得将个人手机带入校园&#xff0c;禁止带入课堂&#xff1b;应设立校内公共电话、建立班主任沟通热线、探索使用具备通话功能的电子学生证或提供其他家…

List操作add,clear,addall报错UnsupportedOperationException的解决办法

ArrayList和Arrays.ArrayList是两码事 ArrayList 支持 add&#xff0c;clear&#xff0c;addall Arrays.ArrayList不支持add&#xff0c;clear&#xff0c;addall 这个方法的使用时候&#xff0c;传递的数组必须是对象数组&#xff0c;而不是基本数据类型 JDK源码 /** *返回由…

LLM之RAG实战(三十六)| 使用LangChain实现多模态RAG

我们之前介绍的RAG&#xff0c;更多的是使用输入text来查询相关文档。在某些情况下&#xff0c;信息可以出现在图像或者表格中&#xff0c;然而&#xff0c;之前的RAG则无法检测到其中的内容。 针对上述情况&#xff0c;我们可以使用多模态大模型来解决&#xff0c;比如GPT-4-V…

【微服务】Sentinel(流量控制)

文章目录 1.基本介绍1.Sentinel是什么2.Sentinel主要特性3.Sentinel核心功能1.流量控制2.熔断降级3.消息削峰填谷 4.Sentinel两个组成部分 2.Sentinel控制台显示1.需求分析2.下载3.运行1.进入cmd2.输入java -jar sentinel-dashboard-1.8.0.jar3.查看默认端口8080 4.访问1.账号和…

数据结构课设-基于Python的校园导航系统(附源码)

一月份的数据结构课设完成后&#xff0c;我对Python的了解也更加深刻。现将课设报告及源码开源&#xff0c;不足之处希望大家指正。源码我放在博客主页的资源中&#xff0c;需要的话大家自行下载&#xff08;用户信息保存在 users.json 文件中&#xff0c;地图信息保存在 campu…

GooglePlay无法下载应用问题

问题如下 解决方法 1、实际上是因为google尚未添加apk downloader扩展程序 2、添加该扩展程序后&#xff0c;在应用中搜索应用名即可 欧克&#xff01;下载完成

IDEA设置内存大小不生效

IDEA设置内存大小不生效 100%可行的方法 -Xms512m -Xmx4096m1.首先要找对idea加载的是哪个配置文件。 2.找到idea启动文件夹&#xff0c;编辑idea.bat 添加打印修改文件路径的代码&#xff0c;运行idea.bat打印一下你的配置文件路径&#xff0c;找到路径 修改 然后运行idea.…

机器学习作业二之KNN算法

KNN&#xff08;K- Nearest Neighbor&#xff09;法即K最邻近法&#xff0c;最初由 Cover和Hart于1968年提出&#xff0c;是一个理论上比较成熟的方法&#xff0c;也是最简单的机器学习算法之一。该方法的思路非常简单直观&#xff1a;如果一个样本在特征空间中的K个最相似&…

vs2022 关于Python项目无法识别中文的解决方法

这是针对于vs2022安装和使用教程&#xff08;详细&#xff09;-CSDN博客 Python项目无法识别中文的解决方法的文章 一、问题 1.输入代码 print("你好Hello world&#xff01;") 2.启动&#xff0c;发现代码里有中文报错 二、解决方法 1.选择菜单栏里的工具->…

超实用的Maven指南

文章目录 实战记录&#x1f4dd;Maven 指令大全 &#x1f31f;找到没有被使用的jar&#xff08;analyze&#xff09;分析jar是被哪个maven引入&#xff08;tree&#xff09;&#x1f31f; dependencies&#xff08;Maven依赖&#xff09;build-resources&#xff08;资源导入&a…

如何提高知识库系统管理水平?

我们都有过这样的经历--遇到问题或紧急请求时&#xff0c;第一时间就是向知识库系统寻求帮助。很多时候&#xff0c;当你翻遍了无穷无尽的文档&#xff0c;却发现没有任何东西能够摆脱此时的困境&#xff0c;这时&#xff0c;向服务台提交工单成了不可避免的解决方式&#xff0…

基于Java的新生入学报到管理系统的设计与实现(论文+源码+PPT)_kaic

摘 要 21世纪的今天&#xff0c;随着社会的不断发展与进步&#xff0c;人们对于信息科学化的认识&#xff0c;已由低层次向高层次发展&#xff0c;由原来的感性认识向理性认识提高&#xff0c;管理工作的重要性已逐渐被人们所认识&#xff0c;科学化的管理&#xff0c;使信息…