C++——基础概念

C++——基础概念
C++是兼容C的,所以我们可以在C++环境下写C语言。

C++之父:本贾尼・斯特劳斯特卢普。

一、命名空间

#include<stdio.h> #include<stdlib.h> int main() { int rand = 10; printf("%d\n", rand);//代码可以正确执行 } 
#include<stdio.h> #include<stdlib.h> int rand = 10; int main() { printf("%d\n", rand);//会编译报错 }

思考:stdlib.h中存在rand这个函数,那么我们确自定义了rand这个变量。当random是局部变量时,代码可以成功执行,当random是全局变量时,代码确会引起编译报错,这是什么原因呢?

下面我们需要了解C++的标识符查找规则和引起标识符命名冲突的本质原因。

(1)C++标识符查找规则

标识符:变量、函数、类、常量、命名空间。

编译器查找标识符时,会从当前最内层的作用域开始查找;如果找到了,就不会再去外层作用域查找。

(2)引起标识符命名冲突的本质原因

同一作用域下不能存在同名的标识符。

那么用怎么样才能避免编译报错呢?

这里就引入了命名空间的概念。

(3)命名空间

①概念

命名空间就是我自己造了一个域,用来存放标识符。用来解决同一个域下命名冲突的问题。

用(命名空间名::标识符)来访问我域里的标识符。

②语法

语法:

namespace 命名空间名 { // 你自定义的标识符 }

③解决标识符冲突问题

#include<stdio.h> #include<stdlib.h> namespace mycode{ int rand = 10; } int main() { printf("%d\n", mycode::rand);//正常打印出10 }

那么在同一个命名空间下,标识符一样该怎么处理,这里我们就需要了解命名空间的嵌套。

(4)命名空间的嵌套

①概念

在父域里辟多个子域。用来解决父域中标识符冲突问题。用(父域命名空间名::子域1::标识符)来访问我域里的标识符。

②语法

namespace 外层命名空间名 { namespace 内层命名空间名1 { // 你自定义的标识符 } namespace 内层命名空间名2 { // 你自定义的标识符 } //...... }

③示例

#include<stdio.h> namespace code{ namespace codeA { int a = 10; } namespace codeB { int a = 20; } } int main() { printf("%d\n", code::codeA::a);//打印10 printf("%d\n", code::codeB::a);//打印20 }

(5)命名空间的使用

①指定命名空间访问

就是我们前面用的::

②展开某个成员访问

就是访问命名空间里的某个成员。

语法:

using 命名空间名::成员;

示例:

#include<stdio.h> namespace name { int a = 3; int b = 6; } int main() { using name::a;//展开某个成员 printf("%d\n", a); }

③展开命名空间的全部成员

语法:

using namespace 命名空间名; 

示例:

#include<stdio.h> namespace name { int a = 3; int b = 6; } int main() { using namespace name;//展开全部成员 printf("%d\n", a); printf("%d\n", b); }

(6)补充

在多个文件使用命名空间时

C++ 不允许同一个作用域下有两个同名的类或变量,但允许同一个命名空间被写在不同的文件、不同的位置,编译器会自动把它们合并成一个。

C++标准库都存放在一个名叫std(standard)的命名空间中。

二、C++的输入输出

(1)<iostream>

是Input Output Stream的缩写,为你提供了输出输出(cin、cout)和操作规则(<<、>>)。

(2)std::cin和std::cout

std::cin是在控制台输入的意思,我们知道C++的标准库都放在std这个命名空间里。所以前面要加上std::,这里面的c是console(控制台)的意思,in是input(输入的意思)。
std::cout与上面唯一的区别是out是输出的意思。所以表示的是在控制台输出的意思。

(3)<< 和 >>

<<是流插入运算符,和cout搭配使用。例如std::cout<<"hello world"。把hello world在终端打印出来。可以一次打印多个内容。
#include<iostream> int main() { using namespace std; cout << "hello world" << endl; cout << "我的刀盾喜欢奶龙" << endl << "咕咕嘎嘎" << endl;//在屏幕上打印两行内容 }
>>是流提取运算符,和cin搭配使用。也可以一次输入多个内容。
#include<iostream> int main() { using namespace std; int a,b; cout << "请输入两个整数:"; cin >> a >> b; cout << "两个数之和为:" << a + b << endl; }

需要注意的C++可以自动识别变量类型,不需要我们手动输入占位符。

(4)endl

endl表示换行的意思。

三、缺省参数

(1)基本概念

定义函数时,给定参数一个默认值(这个值也叫做缺省值)。在传参数时,如果不传参数就使用默认值,传参数就使用你传的参数。
#include<iostream> using namespace std; void Print(int a= 3) { cout << a << endl; } int main() { Print();//打印3 Print(6);//打印6 }

(2)全缺省

给函数的全部参数设置缺省值。

#include<iostream> using namespace std; void Print(int a= 3,int b = 2) { cout << a << "," << b << endl; } int main() { Print();//打印3,2 Print(0,0);//打印0,0 }

(3)半确省

给函数的部分参数设置缺省值。半缺省中,C++规定缺省值必须从右向左连续传。在使用时,必须传够没用缺省值的参数,且是按照从左向右传的顺序。
#include<iostream> using namespace std; void Print(int a, int b = 1, int c = 2) { cout << a << "," << b << ","<< c << endl; } int main() { //Print();//没有传够参数,会触发编译错误 Print(0);//打印0,1,2半缺省起码传一个 Print(6,6);//打印6,6,2 Print(5,5,5);//打印5,5,5 }

函数声明和定义分离时,函数的缺省参数只能出现一次,且出现在声明部分。

#include<iostream> using namespace std; void Print(int a, int b = 3);//函数声明部分(缺省值必须写在声明里) void Print(int a , int b) {//函数定义部分 cout << a << "," << b << endl; } int main() { Print(3);//打印3,3 Print(2,6);//打印2,6 }

四、函数重载

C++规定在同一作用域内,可以存在一个或多个同名函数。但是这个同名函数需要满足参数个数不同 || 参数类型不同  || 参数顺序不同(参数类型的顺序不同)。

#include<iostream> using namespace std; int Add(int a, int b, int c) {//参数个数不同 return a + b + c; } int Add(int a, int b) { return a + b; } int main() { int a = Add(1,1,1); int b = Add(1, 1); cout << a << endl << b << endl; }
#include<iostream> using namespace std; int Add(int x,int y) {//参数类型不同 return x + y; } double Add(double x, double y) { return x + y; } int main() { int a = Add(2, 3); double b = Add(1.2, 1.3); cout << a << endl << b << endl; }
#include<iostream> using namespace std;//参数类型不同 double Calc(int a,double b) { return a + b; } double Calc(double a,int b) { return a - b; } int main() { double a = Calc(2, 2.2); double b = Calc(2.2, 2); cout << a << endl;//a的参数第一个是整型,第二个是浮点型,所以调的是加法函数(4.2) cout << b << endl;//b的参数第一个是浮点型,第二个是整型,所以调的是加法函数(0.2) }

五、引用(reference)

(1)基本概念

给已有的变量起一个别名。直接在原内存上操作这个变量。

(2)语法

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

以我们以前常写的Swap函数为例,我们来体会引用的妙处。

#include<iostream> using namespace std; void Swap(int* a, int* b) {//用指针的写法 int tmp = *a; *a = *b; *b = tmp; } int main() { int a = 2, b = 3; cout << "交换前" << "a = " << a << " " << "b = " << b << endl; Swap(&a, &b); cout <<"交换后" << "a = " << a << " " << "b = " << b << endl; }
#include<iostream> using namespace std; void Swap(int& a, int& b) {//用引用的写法 int tmp = a; a = b; b = tmp; } int main() { int a = 2, b = 3; cout << "交换前" << "a = " << a << " " << "b = " << b << endl; Swap(a, b); cout << "交换后" << "a = " << a << " " << "b = " << b << endl; }

(3)引用的要求

①初始化

对于局部/全局使用引用时必须进行初始化。比如顺序表的初始化,在main函数内部使用引用。
#include<iostream> using namespace std; int main() { int a = 10; int& b = a;//引用时必须进行初始化 cout << &a << endl; cout << &b << endl;//在这里可以看到a,b是同一个地址 }
对于函数形参里的引用,你传的实参就是已经完成了初始化。比如前面的Swap函数。你传完参数,其实就完成了对形参的初始化。

②命名

对于不同作用域下引用的别名可以相同,比如我们上面的Swap函数。同一作用域下引用的别名不能与原变量的名字相同。

③多个引用

一个变量可以有多个引用,一个引用的改变一定会改变原变量和其它引用的值。

#include<iostream> using namespace std; int main() { int a = 2; int& b = a; int& c = a;//这里可以写a也可以写b cout << "变化前" << a << ","<<b << ","<< c << endl; c++; cout << "变化后" << a << "," << b << "," << c << endl;//这里可以观察到 //a,b,c的值均发生了变化 }

④引用的绑定问题

引用一旦绑定某个实体(有内存地址,能存储数据的对象;例如变量、数组),就终身只是这个实体的别名。再次对引用的操作,只是在修改引用的值,内存永远都是那一块。
int main() { int a = 2; int b = 3; int& ra = a; ra = b;//只是赋值操作 //int& ra = b;//重定义错误 }

⑤引用的优点

引用在实践中往往用于函数传参或者做返回值。因为它直接对内存进行操作,就减少了拷贝从而提高了时间效率。

六、const引用

(1)概念

被const修饰的引用。原数据进行const引用后权限只能不变或者变小。

(2)权限变化

原变量权限为只读,const引用后权限还是只读。
int main() { const int a = 3;//权限为只读,不能修改 a = 3的值 const int& ra = a;//权限仍然为只读,不能修改 ra = 3的值 //int& ra = a;//错误写法,权限不能被放大 }
原变量为可读可写,const引用后权限为只读。
int main() { int a = 3;//可读可写 const int& ra = a;//只读 a = 6;//正确写法,没有超出权限 //ra = 3;//错误写法,超出了权限 }

(3)const引用修饰临时变量

①什么是临时变量

变量是指内存空间和存储的值。
临时变量是指为了保证代码能跑,编译器自己制定的一份临时空间。生命周期随着这条语句的执行结束立马销毁。

常见的有单独的常量(表达式算出来没有用变量存储的也算),不同类型转化时触发的临时变量的创建。

②const引用可以修饰临时变量的原因

对于普通的引用只能绑定有内存地址的变量。核心原因是普通的引用可读可写,对于临时变量这种悄么的产物,本身就不安全。你还权限放大,这不就太过分了。
而const引用可以修饰临时变量的主要原因就是你临时变量虽然本身就不安全。反正我只读,都会把你改为安全的,反正不会对你进行操作。唯一的变化就是延长了临时变量的生命周期,const引用什么时候销毁,临时变量什么时候销毁。

③const引用修饰常量

int main() { const int& ra = 20;//安全 int& rra = 20;//不安全,属于权限放大 }
int main() { int a = 20; const int& ra = a * 3;//安全 int& rra = a * 3;//不安全,权限放大 }

④const引用修饰不同类型转化

int main() { int a = 3; const double& ra = a;//安全 double& rra = a;//不安全,权限放大 }

(4)引用和指针的区别

①语法:引用只是给变量取一个别名,不需要单独再开空间。而指针是存储地址的变量,所以需要单独开空间。
②初始化:引用在定义时必须进行初始化;而指针一般建议初始化,避免野指针。
③特性:引用在绑定一个对象后,就不能再改变;而指针绑定一个对象后可以换帮绑。
④访问:引用可以直接访问对象;而指针需要解引用才能访问对象。
⑤大小:引用的大小取决于对象的大小;指针的大小取决于操作系统的位数(32位下占4个字节;64位下占八个字节)
⑥安全性:指针很容易出现空指针和野指针;而引用不存在这个问题。

七、宏

(1)概念

 宏是在预处理阶段,给一段文本(值/表达式/代码)绑定一个宏名(符号),分为有参宏和无参宏。

(2)无参宏

语法:

#define 宏名 替换文本

例子:

#define val 18 int main() { using namespace std; cout << val << endl;//在执行程序是编译器会自动将val换成18 }

(3)有参宏

语法:

#define 宏名 (参数1,参数2,……) 替换文本//括号里是参数列表

例子:

#define Add(a,b) ((a) + (b)) int main() { using namespace std; cout << Add(2, 3) << endl;//输出5 }

注意:这里a和b加括号,原因是为了避免运算符优先级带来的计算错误。

八、inline

(1)概念

被inline修饰的函数叫做内联函数。inline的作用是将代码在调用处展开,不需要再建议栈帧了,提高了效率。

(2)注意

inline只是建议,编译器可以采纳也可以不采纳。一般内联函数都是几行的小代码。
#include<iostream> using namespace std; inline int Add(int a,int b) { return a + b; } int main() { cout << Add(2, 3) << endl;//就是这个意思cout << 2+3 << endl; } 

(3)内联函数和宏的区别

①本质:内联函数是函数;宏是文本替换
②运算符优先级:内联函数不存在问题;宏需要手动添加括号
③调试:内联函数可以调试,宏不能调试
④编译器:编译器可以拒绝内联;编译器不能拒绝宏,宏是强制替换

八、nullptr

(1)概念

nullptr是C++11引入的一个关键字,用来表示空指针。

(2)引入的原因

NULL在C++表示的是0的意思,如果在函数重载时,就会发生到底读取错误。
#include<iostream> using namespace std; void Print(int* x) { cout << "int*" << endl; } void Print(int x) { cout << "int" << endl; } int main() { Print(NULL);//你预期的结果是int,可是运行结果确是int Print(nullptr);//符合预期结果 }

Read more

【C++:哈希表】从哈希冲突到负载因子:熟悉哈希表的核心机制

【C++:哈希表】从哈希冲突到负载因子:熟悉哈希表的核心机制

🔥艾莉丝努力练剑:个人主页 ❄专栏传送门:《C语言》、《数据结构与算法》、C/C++干货分享&学习过程记录、Linux操作系统编程详解、笔试/面试常见算法:从基础到进阶、测试开发要点全知道 ⭐️为天地立心,为生民立命,为往圣继绝学,为万世开太平 🎬艾莉丝的简介: 🎬艾莉丝的C++专栏简介: 目录 C++的两个参考文档 前情提示 1  ~>  初始哈希 2  ~>  直接定址法 2.1  概念 2.2  示例:字符串中的第一个唯一字符 3  ~>  哈希的一些概念 3.1  哈希冲突 3.2  负载因子 3.3

By Ne0inhk

CppCoro终极指南:掌握C++协程异步编程的完整教程

CppCoro终极指南:掌握C++协程异步编程的完整教程 【免费下载链接】cppcoroA library of C++ coroutine abstractions for the coroutines TS 项目地址: https://gitcode.com/gh_mirrors/cp/cppcoro CppCoro是一个基于C++协程技术规范(Coroutines TS)的通用协程库,提供了丰富的异步编程原语,帮助开发者构建高效、可扩展的C++异步应用。本文将带你从入门到精通,全面掌握这一强大库的使用方法和核心原理。 🚀 什么是CppCoro? CppCoro库为C++开发者提供了一套完整的协程抽象,包括任务(task)、生成器(generator)、异步事件等组件,让你能够以同步的代码风格编写高效的异步程序。该库基于N4680中描述的协程技术规范,支持在Windows(Visual Studio 2017+)和Linux(Clang 5.

By Ne0inhk
VSCode 中 C/C++ 安装、配置、使用全攻略:小白入门指南

VSCode 中 C/C++ 安装、配置、使用全攻略:小白入门指南

引言 本文为Windows系统下安装配置与使用VSCode编写C/C++代码的完整攻略,示例机器为Windows11。 通过本文的指导,你可以成功在Windows 机器上上使用VSCode进行C/C++开发。 在文章开始之前,你可以先阅读下面这段话,以便于对步骤有个大致的了解: 首先,从VSCode官网下载并安装VSCode,确保安装路径为全英文;接着,下载并安装MinGW,以提供GCC等编译器,确保其路径也为全英文;然后,配置MinGW的环境变量,使系统能够识别GCC编译器;最后,在VSCode中安装必要的C/C++插件,创建并编译一个简单的C++程序,验证配置的正确性。 文章目录 * 引言 * 一、VSCode下载安装 * 二、MinGw下载安装 * 三、MinGw配置环境变量 * 四、VSCode编写/编译 C/C++代码 * 汉化 * C/C++插件安装 * 测试与使用 * 结语

By Ne0inhk
【Linux】线程池(一)C++ 手写线程池:基于策略模式实现高性能日志模块

【Linux】线程池(一)C++ 手写线程池:基于策略模式实现高性能日志模块

文章目录 * 池化技术 * 线程池的日志模块 * 日志与策略模式 * 日志模块 * 两个核心问题 * 设计文件等级 * 刷新策略 * 获取日志时间 * logger类实现 * 内部类LogMessage实现 * 日志刷新流程图及源码 池化技术 池化技术可以减少很多的底层重复工作,例如创建进程、线程、申请内存空间时的系统调用和初始化工作,例如线程池,先预先创建好一些线程,当任务到来时直接将预先创建好的线程唤醒去处理任务,效率会远远高于任务到来时临时创建线程。例如内存池,但我们要用1mb空间时内存池会一次性申请20mb空间,效率会远远高于用多少空间申请多少空间(申请空间会调用系统调用)。 线程池是执行流级别的池化技术,STL中的空间配置器和内存池是内存块管理级别的池化技术。 线程池的日志模块 下⾯开始,我们结合我们之前所做的所有封装,进⾏⼀个线程池的设计。在写之前,我们要做如下准备。 * 准备线程的封装 * 准备锁和条件变量的封装 * 引⼊日志,对线程进⾏封装 日志与策略

By Ne0inhk