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

【Java 开发日记】我们来说一下消息的可靠性投递

【Java 开发日记】我们来说一下消息的可靠性投递

目录 1. 核心概念 2. 面临的挑战 3. 关键实现机制 3.1 生产端保证 3.2 Broker端保证 3.3 消费端保证 4. 完整可靠性方案 4.1 事务消息方案(如RocketMQ) 4.2 最大努力投递方案 4.3 本地消息表方案(经典) 5. 高级特性与优化 5.1 顺序性保证 5.2 批量消息可靠性 5.3 监控与对账 6. 不同MQ的实现差异 7. 实践建议 总结 面试回答 1. 核心概念 可靠性投递(Reliable

By Ne0inhk
Java 大视界 -- 基于 Java+Kafka 构建高可用消息队列集群:实战部署与性能调优(442)

Java 大视界 -- 基于 Java+Kafka 构建高可用消息队列集群:实战部署与性能调优(442)

Java 大视界 -- 基于 Java+Kafka 构建高可用消息队列集群:实战部署与性能调优(442) * 引言: * 正文: * 一、 Kafka 高可用集群核心认知:先懂原理,再谈部署 * 1.1 Kafka 高可用核心原理 * 1.1.1 核心组件协同逻辑 * 1.1.2 高可用核心:多副本与 Leader 选举机制 * 1.2 Kafka 高可用集群架构设计要点 * 1.3 技术栈选型:Java+Kafka 核心版本适配 * 二、 实战部署:Java+Kafka 高可用集群搭建 * 2.1 部署前准备:环境初始化

By Ne0inhk
Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify

Java内功修炼(2)——线程安全三剑客:synchronized、volatile与wait/notify

1.线程安全 1.1 概念&示例 概念:指在多线程环境下,某个代码、函数或对象能够被多个线程同时调用或访问时,仍能保持正确的行为和数据一致性。简单来说,线程安全的代码在多线程环境下运行可靠,不会因线程间的交互而产生不可预测的结果 示例: publicclassThreadDemo{publicstaticint count =0;publicstaticvoidmain(String[] args)throwsInterruptedException{Thread thread1 =newThread(()->{for(int i =0; i <500000; i++){ count++;}});Thread thread2 =newThread(()->{for(int i =0; i <500000;

By Ne0inhk
JAVA最新版本详细安装教程(附安装包)

JAVA最新版本详细安装教程(附安装包)

目录 文章自述 一、JAVA下载 二、JAVA安装 1.首先在D盘创建【java/jdk-23】文件夹 2.把下载的压缩包移动到【jdk-23】文件夹内,右键点击【解压到当前文件夹】 3.如图解压会有【jdk-23.0.1】文件 4.右键桌面此电脑,点击【属性】 5.下滑滚动条,点击【高级系统设置】 6.点击【环境变量】 7.找到系统变量(S),然后点击【新建】 8.输入变量名和变量值 9.确认无误,点击【确定】 10.继续点击系统变量下的【新建】 11.输入变量名和变量值

By Ne0inhk