【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.hqwc.cn/news/576911.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java_18 字符串中的单词反转

字符串中的单词反转 你在与一位习惯从右往左阅读的朋友发消息&#xff0c;他发出的文字顺序都与正常相反但单词内容正确&#xff0c;为了和他顺利交流你决定写一个转换程序&#xff0c;把他所发的消息 message 转换为正常语序。 注意&#xff1a;输入字符串 message 中可能会…

Halcon3D倾斜平面矫正至水平面

前言 在相当多的3d检测中&#xff0c;由于各种因素的干扰&#xff0c;我们所检测的平面通常并不是一个水平面&#xff0c;或者被检测的面不是水平面的情况。尤其是在倾斜面的缺陷检测和平面度检测中&#xff0c;使用被测面与拟合基准面进行计算很难做到准确的定位到缺陷的情况…

git:git rm --cached和git rm -f和git restore --staged的区别(附带详细步骤测试)和git diff比较本地分支和远程分支的区别(细分到文件/文件)

git rm --cached和git rm -f和git restore --staged的区别 当试图删除一个已经git add在暂存区的文件&#xff0c;我们使用 git rm --cached&#xff1a;从暂存区中移除&#xff0c;但保留在工作区中&#xff0c;且工作区中的文件内容在执行命令前需要还原到最后一次git add的…

Python程序怎么打包成exe文件

前言 pyinstaller可以将.py文件打包成.exe可执行文件&#xff0c;即使别人的电脑上没有搭建Python环境&#xff0c;也是可以直接运行程序的。 pyinstaller安装 首先打开cmd&#xff0c;在里面输入下面这一行命令&#xff0c;回车即可。 pip install pyinstaller 我运行命令…

网络七层模型之表示层:理解网络通信的架构(六)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

MYSQL数字函数实操宝典:场景化SQL语句一网打尽

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》《MYSQL应用》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MYSQL数字函数&#xff1a;不可不知的数据处理利器 文章目录 Part 1: 准备 &#x…

基于springboot+vue+Mysql的财务管理系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

python实现app自动化时一直连不上Appium

背景 在尝试python做APP自动化测试时&#xff0c;发现连不上Appium&#xff0c;我是连接模拟器里的APP 过程 一直以为是我调用初始化APP类方法时&#xff0c;实例化不对&#xff1b;一直翻阅资料也没有个所以然&#xff1b;后来发现模拟器APP的版本是Android 12&#xff1b;py…

正弦实时数据库(SinRTDB)的使用(6)-历史插值查询

前文已经将正弦实时数据库的使用进行了介绍&#xff0c;需要了解的可以先看下面的博客&#xff1a; 正弦实时数据库(SinRTDB)的安装 正弦实时数据库(SinRTDB)的使用(1)-使用数据发生器写入数据 正弦实时数据库(SinRTDB)的使用(2)-接入OPC DA的数据 正弦实时数据库(SinRTDB)…

Voodoo中国区刘毅:全球爆款休闲游戏的创意选品与研发发行 | TopOn观察

10月28日&#xff0c;由TopOn联合罗斯基主办的“游戏赛道新机会”主题沙龙在成都举办。活动邀请了多位国内外知名公司及游戏爆款产品的负责人分享&#xff0c;分别从各自的方向及经验出发&#xff0c;以数据、案例、产品分析、行业趋势等多个维度&#xff0c;为行业从业者带来独…

设计模式 —— 设计原则

在软件开发中&#xff0c;为了提高软件系统的可维护性和可复用性&#xff0c;增加软件的可扩展性和灵活性&#xff0c;程序员要尽量根据6条原则来开发程序&#xff0c;从而提高软件开发效率、节约软件开发成本和维护成本。 开闭原则 对扩展开放&#xff0c;对修改关闭。在程序需…

记CMakeLists.txt中已经配置-fPIC参数但未生效的解决方案

文章目录 1. 问题背景2. CMake中添加-fPIC的方式3. 检查-fPIC是否添加成功4. CMakeLists.txt中已经配置-fPIC参数但未生效的解决方案 1. 问题背景 博主的库会被编译成一个供第三方使用的静态库&#xff0c;第三方在使用博主的库编译动态库时&#xff0c;若不加-fPIC会报unreso…