【C++ 入门精讲1】初始化、const、引用、内联函数 | 超详细手写笔记(附完整代码)

**阅读提示:**本篇笔记完全基于本人手写代码注释整理,每个知识点都结合通俗解读,适合 C++ 新手入门、复习,也可用于面试基础准备,代码可直接复制上机验证。

前言

本篇博客是我学习 C++ 基础语法的完整手写笔记,包含变量初始化、输入输出、const 特性、指针与引用、引用交换、const 引用、内联函数等核心知识点。所有内容均来自本人代码注释,不添加多余内容,结构原汁原味,每个知识点都像"函数调用过程"那样,拆解开讲清楚,方便理解和记忆。

一、头文件与宏定义、外部变量声明

知识点

1. 宏定义 _CRT_SECURE_NO_WARNINGS 必须放在最前面,目的是避免 Visual Studio 编译器报安全警告,因为后续可能会用到一些 C 语言中的库函数(如 string.h 中的函数),不加这个宏定义会出现编译警告。

2.++#include<iostream>++是 C++ 标准输入输出流的头文件,我们用到的 cout(输出)、cin(输入)、endl(换行),都需要包含这个头文件才能使用。

3.using namespace std; 是简化代码的关键,std 是 C++ 标准库的命名空间,所有标准库的内容(如 cout、cin)都在这个命名空间下。如果不加这句话,每次使用 cout 都要写 std::cout,非常繁琐,加了这句话就可以直接写 cout。

  1. extern int n; 这句话只是变量的声明,不是定义。声明的意思是"告诉编译器,有一个int类型的变量n存在,它在其他地方定义",声明不会给变量分配内存,只是让编译器认识这个变量,避免编译报错。

对应代码

cpp 复制代码
#define _CRT_SECURE_NO_WARNINGS // 宏定义放在最前面,避免编译警告 
#include<iostream> // 标准输入输出流 #include<assert.h> #include<string.h> #include<stdlib.h> 
using namespace std; 
extern int n; // 只是声明

二、C 语言与 C++ 结构体的定义区别

知识点

++1. C 语言中,定义结构体必须使用typedef struct 的格式,定义完成后,使用结构体变量时,要么写 struct 结构体名 变量名,要么用 typedef 定义的别名(如下面的 addl)来定义变量,否则会编译报错。++

++2. C++ 中,定义结构体可以简化,不需要写 typedef,直接写 struct 结构体名 即可。使用的时候,也可以直接用结构体名定义变量(如注释中写的add2 ab;),不用再加 struct 关键字,比 C 语言更简洁。++

++3. 补充:C++ 结构体支持直接用大括号初始化,后续在变量初始化部分会详细用到,这也是 C++ 比 C 语言更便捷的地方。++

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
// c语言定义的结构体 
typedef struct addl { int a1; int b1; }addl; 
// c++定义的结构体可以简化为 
// struct add2 { // int a1; // int b1; // }; 
// 调用的时候可以直接add2 ab;

三、指针与引用实现交换函数

知识点(基于本人手写注释,详细解读)

  1. 指针方式实现交换(swap_val 函数):核心是通过传递变量的地址,再通过解引用(*a、*b)修改地址对应的值,从而实现两个变量的交换。需要注意的是,调用这个函数时,必须传递变量的地址(如 &x、&y),否则无法修改原变量的值。

  2. 引用方式实现交换(swap_val2 函数):引用是变量的别名,相当于给变量起了一个小名,操作引用就相当于操作原变量。所以这个函数直接传递变量本身,不需要传地址,语法更简洁,也更安全,不会出现空指针的问题(引用必须初始化,不能为 NULL)。

  3. 两者对比:引用交换比指针交换更简洁、更安全,这也是 C++ 引入引用的重要原因之一,在实际开发中,能用引用的地方尽量不用指针(除非有特殊需求)。

对应代码

cpp 复制代码
// 以指针的方式实现交换 
void swap_val(int* a, int* b) { int temp = *a; *a = *b; *b = temp; }
 // 以引用的方式实现交换 
void swap_val2(int& a, int& b) { int temp = a; a = b; b = temp; }

四、内联函数的基本定义与特性

知识点

1. 内联函数的声明:在函数返回值类型前面加上 inline 关键字,就可以将函数声明为内联函数(如 add 函数),内联函数的核心作用是提高代码运行效率。

2. 内联函数的优点:效率高,本质是"用空间换时间"。普通函数调用会有一系列繁琐的流程(后续会详细讲),而内联函数会在编译时直接展开,跳过这些流程,从而提高运行速度。

3. 内联函数的特点:必须是体积小、逻辑简单的函数(比如 add 函数,只有一句 return a + b),如果函数体很长、有复杂的循环或判断,编译器可能不会将其当作内联函数处理(即使加了 inline 关键字)。

4. 对比普通函数:sub 函数没有加 inline,是普通函数,调用时会执行完整的函数调用流程,效率比内联函数低。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
// 内联函数示例 
int inline add(int a, int b) 
{ return a + b; } 
int sub(int a, int b)
 { return a - b; }

五、C++ 变量初始化方式(重点)

知识点(基于本人手写注释,详细解读)

1. C 风格初始化:就是我们最熟悉的 char c = 'A';int n = 100;,这种方式和 C 语言完全一致,简单易懂,但不够严谨。

2. 小括号初始化:格式是 变量类型 变量名(初始值);,比如 char c4('B');int n2(4.5);。需要注意的是,这种初始化方式不做类型检查,会自动进行强制类型转换,比如 n2 是 int 类型,初始值是 4.5,会自动强转为 4,不会报错,这是它的缺点。

3. 大括号初始化(C++ 推荐):格式是 变量类型 变量名{初始值};,比如 char c6{100};。这种方式是 C++ 新增的,最大的优点是严格类型检查,不允许隐式的缩窄转换(比如 int n3 = {4.5}; 会编译报错,因为 4.5 是浮点型,n3 是 int 类型,精度会丢失,编译器会阻止这种操作)。

4. 大括号初始化的小细节:大括号里面加入一个逗号也没关系,比如 char c7{98,};,这是 C++ 允许的,不会报错,但小括号初始化不允许这样写。

5. 易错点:int a(); 这句话不是变量声明,而是函数声明,意思是"声明一个返回值为 int 类型、无参数的函数 a",很多新手会误以为这是初始化变量 a,这里一定要注意区分。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
// c.变量初始化 
char c = 'A'; char c2; int n = 100; 
// c++初始化 
// 建议使用大括号来进行初始化。因为c++是严格类型检查 
char c3 = ('A'); char c4('B'); int a();//为函数的声明。不是变量的声明 
int n2(4.5);//等价于int n2=4.5; 并没有类型的检查,它会自动进行强转 
char c5 = { 97 }; 
char c6{ 100 }; 
char c7{ 98, };//大括号里面加入一个逗号也没关系,小括号不可以 
// int n3 = { 4.5 };这句话编译不通过,因为c++是严格类型检查的,它会检查4.5这个值的类型,发现是浮点型,而n3是int类型,所以编译不通过 // 由此我们可以看出小括号赋值是不会进行类型检查的,和c语言标准的赋值语句是一样的。

六、C++ 输入与输出

知识点

  1. 输出(cout):C++ 的输出使用 cout << 内容,和 C 语言的 printf 相比,最大的优势是自动识别变量类型,不需要指定占位符(比如 %d、%c),直接写变量名即可。

  2. 输出符号与换行:<< 是输出插入符号,作用是将后面的内容插入到输出流中;endl 是一个函数,作用是输出换行符,并且刷新缓冲区,确保内容立即显示在屏幕上(和 C 语言的 \n 类似,但多了刷新缓冲区的功能)。

  3. 输入(cin):C++ 的输入使用cin >> 变量名,同样自动识别类型,不需要占位符。需要注意的是,在没有重载的情况下,cin 只能输入基本数据类型(int、char、double 等),不能直接输入结构体。

  4. 结构体的输入输出:结构体不能直接用 cin >> 或 cout << 操作,必须访问结构体的成员(比如 ab.a1、abc.b1),才能对结构体进行输入输出。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
cout << "hello world" << endl;
int a1(6); char c1{ 97 }; 
double d1{ 10.5 }; 
cout << a1 << " " << c1 << " " << d1 << endl;
//c++输出,自动识别变量的类型,不需要指定的占位符。(输入也同理) 
// "<<"输出插入符号,">>"输入提取符号,endl 是返回换行符的表示(函数) 
// c++的输入 // >>提取符,从键盘中提取输入的内容并赋值到指定的变量。
// int d; 
// cin >> d; 
// cout << a1,c1; addl ab{}; addl abc{ 3,5 };
//结构体可以直接用大括号直接进行赋值
 // cin >> a;错误,在未重载的情况下,只能是基本数据类型。
 // cin >> ab.a1 >> ab.b1; cout << abc.a1 << " " << abc.b1 << endl;

七、C++ 中 const 关键字的特性(与 C 语言的区别,重点)

知识点

1. C 语言与 C++ 中 const 的核心区别:在 C 语言中,const 定义的变量,虽然叫做"常量",但底层仍然被看作变量,不能用于定义数组大小;而在 C++ 中,const 定义的变量,底层是真正常量,编译时会直接将常量名替换为它的初始值,因此可以用于定义数组大小。

2. C++ 中 const 常量的使用:比如 const int n12 = 100;,编译时,所有用到 n12 的地方,都会直接被替换成 100,所以 int arr[n12]{0}; 是合法的,这在 C 语言中是不允许的。

3. C++ 中修改 const 常量的方法:const 常量本身是不能直接修改的,比如 n12 = 200; 会编译报错。但可以通过 const_cast 强制去掉 const 属性,比如 int* n2p = const_cast<int*>(&n12);,然后通过指针解引用修改内存中的值(*n2p = 200)。

4. 易错点:即使通过 const_cast 修改了内存中的值,输出常量名(n12)时,仍然会输出初始值 100。因为编译时,n12 已经被替换成 100 了,编译器不会再去访问内存中的实际值,就相当于宏定义在预编译时的替换特性,无脑直接替换,不看内存。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
int n1 = 20; 
const int n12 = 100;
//在c语言中,const定义的变量,虽说是常量,但在c语言中底层仍看作变量 
// c++中,const定义的变量,底层是常量,不能修改,编译器会报错。 
// 即,编译时,使用到const变量的位置直接替换为常数。这于c语言中的const的特性是有所不同的。 
int arr[n12]{ 0 };
//n12直接被替换为100. 
// c++中修改const变量时,必须进行转换 
int* n2p = const_cast<int*>(&n12); 
*n2p = 200; 
cout << n12 << endl;
//因为在预编译的阶段,n12是常量,在c++中,常量在预编译的时候会直接被它的定义语句中的值所替换,即n12被预编译的时候直接被替换为100, 
// 就相当于告诉计算机,你直接把这个常量名等于100就可以了(相当于宏定义在预编译时的特性),不需要去看内存里面存储的实际值 
// 无脑直接将100替换n12,不看内存的值,所以这里输出的是100 
cout << *n2p << endl;

八、const 修饰指针的权限规则(重点)

知识点

1. 核心规则:指针的权限只能"强变弱"(能力缩小),不能"弱变强"(能力放大)。这里的"权限"指的是指针操作内存的能力,指针能力的强弱,只与变量名前的 const 有关,与指针后面的 const 无关,含 const 的指针能力更弱。

2. 四种指针的权限对比(从强到弱):

(1)int* np1:能力最强,可以修改指向空间中的值,也可以重新指向新的空间(比如 np1 = &m)。

(2)const int* np2:能力减弱,只能读取指向空间中的值,不能修改(比如 *np2 += 1 会报错),但可以重新指向新的空间(比如 np2 = &m)。

(3)int* const np4:能力减弱,指针指向的地址不能改变(比如 np4 = &m 会报错),但可以修改指向空间中的值(比如 *np4 = 20)。

(4)const int* const np3:能力最弱,完全只读,既不能修改指向空间中的值,也不能重新指向新的空间。

3. 补充:const int m = 20;int const m = 20; 是完全等价的,两种写法没有区别,都是定义一个 const 常量 m。

对应代码

cpp 复制代码
    //关于const指针之间的转换,我们引用指针能力的概念,指针能力的强弱只与变量名前的const有关,与它之后的无关,
    // 并且含const的指针能力弱。只能强变弱,不能弱变强,即只能能力缩小,不能增大
    // const 修饰指针的情况:能力可以缩小,不可以放大
#if 0
    // const 修饰指针的情况:  灵活能力可缩小或不变,不可放大(这个灵活能力以最前面的const为基准 不看例如const int * const n1中的第二个const)。
    int n = 10;
    const int m = 20;
    // int const m = 20;这个写法与上面那句话是相同的,及等价。
    // 一、如何存储  &n=> int * 的结果
    // int * => 数据类型  *    能力最强(可修改指向空间中的值,可重新指向新空间)
    // const 修饰的指针能力    能力弱了

    // 1) int * 接  &n
    int* np1 = &n;

    // 2) const int * 接 &n  => 能力缩小
    const int* np2 = &n;
    // 只能读数据,不能修改指向空间的数据,因为np2它是常量指针,它所存储的数据是常量不可修改。
    // *np2 += 1;  // error
    int x = *np2 + 5; // OK, 只读的
    np2 = &m; // OK, 重新指向新的空间

    int* const np4 = &n;
    // 这个值所指向的地址里卖存储的值可以修改但是指向的地址值不可改变
    // 3) const int * const 接 &n   => 能力缩小

    const int* const np3 = &n;
    int y = *np3 + 100; // 只读

九、引用 & 的核心概念(重点)

知识点

  1. 引用的语法:数据类型 & 引用名 = 变量名;,比如 int& r = n_ref;。引用名是变量的别名,通俗来说,就是给同一个内存空间起了另一个名字,操作引用和操作原变量完全一样。

2. 引用的核心特性:必须初始化,不能出现空赋值(比如 int& r; 会编译报错),因为引用是变量的别名,必须明确它是谁的别名。

3. 引用的易错点:r = m_ref; 这句话不是更改引用的绑定对象,不是让 r 变成 m_ref 的别名,而是用 m_ref 的值给 r 赋值,本质是给原变量 n_ref 赋值(因为 r 是 n_ref 的别名),所以执行完这句话后,n_ref 的值会变成 20。

4. 引用的限制:没有二级引用(比如 int&& r; 是错误的),但可以对引用取地址(比如int* p = &r;),对引用取地址,本质就是对原变量取地址,因为引用和原变量共用一块内存。

5. 引用的高级用法:支持指针引用和数组引用。指针引用是给指针起别名(比如 int* &ptr = p;),数组引用是给数组起别名(比如 int (&ptrr2)[10] = arr;),数组引用的写法要注意,& 要紧跟引用名,括号不能少。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
//语法:数据类型 & 引用名  = 变量名
// 引用名 是变量的别名,是变量的本身,其可以通俗的理解为在同一个内存空间上去另外一个名字,也叫做小名。
// 对于普通的引用,普通引用之所以普通,是因为这个普通引用初始化的对象必须是变量,它是不能对常量进行引用的。
// 针对这个缺陷,我们引进了const引用,它也叫做万能引用,它即可以初始化常量,也可以初始化变量
/*我们知道数据是以二进制的形式存储在内存中,它原本只是一串数字,它是什么类型,只不过是我们赋予它的一个性质。
而这个特殊的const引用,相当于给原来的数据起了一个新的别名,并且叫这个别名的时候你得遵守一些规则,拥有一些性质,
这里的性质就是这是个const常量,常量可以用常量进行初始化,也可以被变量进行初始化,我们将引用的特性结合const的特性,解决了引用初始化的问题*/
int n_ref = 10;  // 重命名避免冲突
int m_ref = 20;
int& r = n_ref;//引用不能出现空赋值,必须初始化。
r = m_ref;//不是更改位置,是ref代替n赋值为m变量的值。
// 引用是没有二级引用的,即引用之后不能再次引用,但是不代表不可以在引用名前加上一个取地址符(因为在等号右侧,是取地址符),这是对引用取地址,即代表对变量取地址。

int* p = &r;//p为一级指针。
int* &ptr= p;//ptr为指针的引用。
int arr[10] = { 0 };
int (&ptrr2)[10] = arr;//引用的引用,即引用的数组,这个引用的名字和数组的定义一样,只不过要在引用名字前面加一个&罢了。

十、const 引用(万能引用)

知识点(详细解读)

1. 普通引用的缺陷:普通引用(非 const 引用)初始化的对象必须是变量,不能对常量进行引用(比如int& r = 1; 会编译报错),这是普通引用的局限性。

2. const 引用的优势:const 引用也叫做"万能引用",它解决了普通引用的缺陷,既可以初始化常量,也可以初始化变量,还可以初始化 const 变量,适用范围更广。

3. const 引用的三种初始化场景:

(1)初始化值为常量:比如 const int& len = 1;,合法,这是普通引用做不到的。

(2)初始化值为普通变量:比如 const int& xr = x;,合法,此时 xr 是 x 的别名,但只能读取 x 的值,不能修改 x 的值(因为是 const 引用)。

(3)初始化值为 const 变量:比如 const int& len2_r = len2;,合法,和初始化普通变量类似,只能读取 len2 的值。

4. 易错点:普通引用不能初始化 const 引用(或常量),比如 int& y = len2_r; 会编译报错,因为 len2_r 是 const 引用,能力更弱,普通引用能力更强,不能"弱变强",违反了指针/引用的权限规则。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
// const 引用:"万能引用"
const int& len = 1;  // 1. 初始化值为常数

int x = 10;
const int& xr = x;   // 2. 初始化值为 变量

const int len2 = 20;
const int& len2_r = len2; //3. 初始值为 const变量

// int& y = len2_r;  //error, 普通引用是无法初始值为const引用(常数)

十一、函数调用过程 + 内联函数展开机制(重点)

知识点

首先,我们先搞清楚:调用普通函数时,系统会做哪些事情(以 sub 函数为例):

1. 保存现场:因为函数调用是在主函数内进行的,我们要跳出主函数,去执行其他函数,就相当于我们工作出差,出差前要把家门锁好、整理好工作环境,避免回来后环境被破坏,这个"锁门、整理环境"的动作,就是保存现场,目的是为了后续能顺利回到主函数继续执行。

2. 将函数入栈:函数调用时,会在栈内存中为函数分配一块空间,用于存储函数的参数、局部变量等,这个过程就是函数入栈。

3. 实参入栈:将函数调用时传递的实参,按照"先右后左"的方向(大多数编译器都是这样),压入栈中,供函数内部使用。

4. 调用函数功能并获取结果:执行函数体内部的代码,完成函数的功能,计算并返回结果。

5. 恢复现场,处理结果,回收栈空间:函数执行完成后,要恢复之前保存的主函数环境(恢复现场),将函数的返回结果传递给主函数,然后释放函数在栈中占用的空间(回收栈空间),最后回到主函数,继续执行后续代码。

然后,我们看内联函数的展开机制(以 add 函数为例):

add 是 inline 内联函数,它和普通函数的调用过程完全不同,内联函数会在编译时直接展开,也就是说,会将 add 函数体的功能(return a + b)直接拿出来,搬到主函数内调用 add 函数的地方。

简单来说,就是"将出差干的事情,直接搬到家里来干",不需要出差(不需要跳出主函数),所以不需要保存现场、函数入栈、实参入栈、恢复现场这些繁琐的流程,也不需要传递参数,速度更快。

举个例子:int ret = add(10, 20); 会被编译器直接展开为 int ret = 10 + 20;,相当于直接在主函数内计算 10+20,效率大大提高。

对应代码

cpp 复制代码
// 调用add函数时,系统做了那些事? 1.保存现场(因为函数调用是在主函数内进行的,我们要去其他函数去处理问题时,要跳出主函数,就相当于我们工作出差,出差的过程要把家门锁好,免得有坏人来破坏,所以,锁门的动作就相当于保护现场) 
// 2.将函数入栈 3. 以先右后左方向将实参入栈(大多数函数是)  4. 调用函数功能并获取结果 5.恢复现场,处理结果, 同时回收上一次函数的栈空间
// add是inline函数,则会直接展开
// inline函数的展开,即将函数体的功能直接拿出来使用,什么意思呢,就是将这个函数的实现内容及代码直接搬到主函数内调用这个函数的地方,也叫做内嵌,或者叫做内联函数的展开,不需要传递参数,相当于将我们出差干的事情搬到加里面去干了,速度更快。
// inline函数在调用时,不会像普通函数那样经过创建函数空间、传递参数...等复杂的流程
int ret = add(10, 20);
cout << ret << endl;
//因为add是内联函数,就相当于cout<< 10+20<<endl;不需要传参,直接将10和20进行add函数内部的a + b操作(函数内部的实现内容)。
int ret2 = sub(30, 5);
cout << ret2 << endl;

;

十二、内联函数与有参宏的区别(面试重点)

这是面试高频考点,核心区别有两点,一定要记牢:

1. 展开时机不同:有参宏是在预处理阶段进行文本替换(无脑替换),而内联函数是在编译阶段进行展开。预处理阶段只是简单的文本替换,不做任何语法检查;编译阶段会进行语法检查、类型检查,更严谨。

2. 是否有类型检查:有参宏是直接进行文本替换,没有任何类型检查,比如宏定义 #define ADD(a,b) a+b,如果传递的是字符类型(ADD('a','b')),也会直接替换成 'a'+'b',不会报错;而内联函数有严格的类型检查,比如 add 函数的参数是 int 类型,如果传递字符类型,编译器会报错,更安全。

补充:内联函数的设计要求的是"体积小、逻辑简单",不能有复杂的循环、判断语句,否则编译器不会将其当作内联函数处理,相当于白加了 inline 关键字。

对应代码(保留本人原代码,无任何修改)

cpp 复制代码
// 内联函数设计时: 1)体积小  2) 逻辑简单(不带复杂的循环)

// 内联函数与有参宏的区别?  【面试题】
// 1) 有参宏是预处理时替换(展开的), 而内联函数是编译时展开
// 2) 有参宏是直接文本替换(无脑),而内联函数具有类型或语法检查(有脑)
int ret = add(10, 20);
cout << ret << endl;
int ret2 = sub(30, 5);
cout << ret2 << endl;

十三、测试函数与主函数结构

知识点

  1. 测试函数的作用:将不同的功能拆分成独立的测试函数(比如 test_basic_syntax、test_swap 等),每个函数负责测试一类语法(比如基础语法、交换函数、const 引用、内联函数),这样结构更清晰,便于调试和学习。

  2. 测试函数的开关:每个测试函数内部都有 #if 1,这个是预处理指令,1 表示开启该测试块,0 表示关闭。如果不想测试某个功能,只需要将 1 改成 0 即可,非常方便。

  3. 主函数的作用:主函数是程序的入口,负责统一调用各个测试函数,执行所有的测试逻辑。程序运行时,会先进入 main 函数,然后依次调用 test_basic_syntax、test_swap、test_const_ref、test_inline,最后返回 0,程序结束。

对应代码

cpp 复制代码
// 测试初始化、输入输出、const、引用等基础语法
void test_basic_syntax() {
#if 1  // 改为1可执行此测试块
    // 函数体内容(前面已拆分)
#endif
}

// 测试交换函数(指针/引用)
void test_swap() {
#if 1  // 改为1可执行此测试块
    int x = 10, y = 100;
    cout << "x=" << x << ",y=" << y << endl;
    // swap_val(&x, &y);  // 指针版交换
    swap_val2(x, y);     // 引用版交换
    cout << "x=" << x << ",y=" << y << endl;
#endif
}

// 测试const引用(万能引用)
void test_const_ref() {
#if 1  // 改为1可执行此测试块
    // 函数体内容(前面已拆分)
#endif
}

// 测试内联函数
//内联函数优点:效率高,用空间换时间。
//内联函数的特点:体积小,结构简单
void test_inline() {
#if 1  // 改为1可执行此测试块
    // 函数体内容(前面已拆分)
#endif
}

// 主函数:统一调用各个测试函数
int main()
{
    // 按需开启测试(修改对应函数内的#if 1/#if 0)
    test_basic_syntax();  // 基础语法测试
    test_swap();          // 交换函数测试
    test_const_ref();     // const引用测试
    test_inline();        // 内联函数测试

    return 0;
}

附录:本人原版完整代码(未做任何修改,可直接复制上机)

cpp 复制代码
#if 0
#define _CRT_SECURE_NO_WARNINGS  // 宏定义放在最前面,避免编译警告
#include<iostream>               // 标准输入输出流
#include<assert.h>
#include<string.h>
#include<stdlib.h>
using namespace std;

extern int n;  // 只是声明

// c语言定义的结构体
typedef struct addl {
    int a1;
    int b1;
}addl;

// c++定义的结构体可以简化为
// struct add2 {
//     int a1;
//     int b1;
// };
// 调用的时候可以直接add2 ab;

// 以指针的方式实现交换
void swap_val(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 以引用的方式实现交换
void swap_val2(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// 内联函数示例
int inline add(int a, int b) {
    return a + b;
}

int sub(int a, int b) {
    return a - b;
}

// 测试初始化、输入输出、const、引用等基础语法
void test_basic_syntax() {
#if 1  // 改为1可执行此测试块
    // c.变量初始化
    char c = 'A';
    char c2;

    int n = 100;

    // c++初始化
    // 建议使用大括号来进行初始化。因为c++是严格类型检查
    char c3 = ('A');
    char c4('B');
    int a();//为函数的声明。不是变量的声明

    int n2(4.5);//等价于int n2=4.5;   并没有类型的检查,它会自动进行强转

    char c5 = { 97 };
    char c6{ 100 };
    char c7{ 98, };//大括号里面加入一个逗号也没关系,小括号不可以

    // int n3 = { 4.5 };这句话编译不通过,因为c++是严格类型检查的,它会检查4.5这个值的类型,发现是浮点型,而n3是int类型,所以编译不通过
    // 由此我们可以看出小括号赋值是不会进行类型检查的,和c语言标准的赋值语句是一样的。
    //--------------------------------------------------------------------------------------------------------------------------------------------------------------------

    cout << "hello world" << endl;

    int a1(6);
    char c1{ 97 };
    double d1{ 10.5 };
    cout << a1 << " " << c1 << " " << d1 << endl;//c++输出,自动识别变量的类型,不需要指定的占位符。(输入也同理)
    // "<<"输出插入符号,">>"输入提取符号,endl 是返回换行符的表示(函数)

    // c++的输入 
    // >>提取符,从键盘中提取输入的内容并赋值到指定的变量。
    // int d;
    // cin >> d;
    // cout << a1,c1;
    addl ab{};
    addl abc{ 3,5 };//结构体可以直接用大括号直接进行赋值
    // cin >> a;错误,在未重载的情况下,只能是基本数据类型。
    // cin >> ab.a1 >> ab.b1;

    cout << abc.a1 << " " << abc.b1 << endl;
    //-----------------------------------------------------------------------------------------------------------------------------------------------------------------
    int n1 = 20;
    const int n12 = 100;//在c语言中,const定义的变量,虽说是常量,但在c语言中底层仍看作变量
    // c++中,const定义的变量,底层是常量,不能修改,编译器会报错。
    // 即,编译时,使用到const变量的位置直接替换为常数。这于c语言中的const的特性是有所不同的。
    int arr[n12]{ 0 };//n12直接被替换为100.

    // c++中修改const变量时,必须进行转换
    int* n2p = const_cast<int*>(&n12);
    *n2p = 200;
    cout << n12 << endl;//因为在预编译的阶段,n12是常量,在c++中,常量在预编译的时候会直接被它的定义语句中的值所替换,即n12被预编译的时候直接被替换为100,
    // 就相当于告诉计算机,你直接把这个常量名等于100就可以了(相当于宏定义在预编译时的特性),不需要去看内存里面存储的实际值
    // 无脑直接将100替换n12,不看内存的值,所以这里输出的是100
    cout << *n2p << endl;

    //关于const指针之间的转换,我们引用指针能力的概念,指针能力的强弱只与变量名前的const有关,与它之后的无关,
    // 并且含const的指针能力弱。只能强变弱,不能弱变强,即只能能力缩小,不能增大
    // const 修饰指针的情况:能力可以缩小,不可以放大
#if 0
    // const 修饰指针的情况:  灵活能力可缩小或不变,不可放大(这个灵活能力以最前面的const为基准 不看例如const int * const n1中的第二个const)。
    int n = 10;
    const int m = 20;
    // int const m = 20;这个写法与上面那句话是相同的,及等价。
    // 一、如何存储  &n=> int * 的结果
    // int * => 数据类型  *    能力最强(可修改指向空间中的值,可重新指向新空间)
    // const 修饰的指针能力    能力弱了

    // 1) int * 接  &n
    int* np1 = &n;

    // 2) const int * 接 &n  => 能力缩小
    const int* np2 = &n;
    // 只能读数据,不能修改指向空间的数据,因为np2它是常量指针,它所存储的数据是常量不可修改。
    // *np2 += 1;  // error
    int x = *np2 + 5; // OK, 只读的
    np2 = &m; // OK, 重新指向新的空间

    int* const np4 = &n;
    // 这个值所指向的地址里卖存储的值可以修改但是指向的地址值不可改变
    // 3) const int * const 接 &n   => 能力缩小

    const int* const np3 = &n;
    int y = *np3 + 100; // 只读
#endif

    //语法:数据类型 & 引用名  = 变量名
    // 引用名 是变量的别名,是变量的本身,其可以通俗的理解为在同一个内存空间上去另外一个名字,也叫做小名。
    // 对于普通的引用,普通引用之所以普通,是因为这个普通引用初始化的对象必须是变量,它是不能对常量进行引用的。
    // 针对这个缺陷,我们引进了const引用,它也叫做万能引用,它即可以初始化常量,也可以初始化变量
    /*我们知道数据是以二进制的形式存储在内存中,它原本只是一串数字,它是什么类型,只不过是我们赋予它的一个性质。
    而这个特殊的const引用,相当于给原来的数据起了一个新的别名,并且叫这个别名的时候你得遵守一些规则,拥有一些性质,
    这里的性质就是这是个const常量,常量可以用常量进行初始化,也可以被变量进行初始化,我们将引用的特性结合const的特性,解决了引用初始化的问题*/
    int n_ref = 10;  // 重命名避免冲突
    int m_ref = 20;
    int& r = n_ref;//引用不能出现空赋值,必须初始化。
    r = m_ref;//不是更改位置,是ref代替n赋值为m变量的值。
    // 引用是没有二级引用的,即引用之后不能再次引用,但是不代表不可以在引用名前加上一个取地址符(因为在等号右侧,是取地址符),这是对引用取地址,即代表对变量取地址。

    int* p = &r;//p为一级指针。
    int* &ptr= p;//ptr为指针的引用。
    int arr[10] = { 0 };
    int (&ptrr2)[10] = arr;//引用的引用,即引用的数组,这个引用的名字和数组的定义一样,只不过要在引用名字前面加一个&罢了。
#endif
}

// 测试交换函数(指针/引用)
void test_swap() {
#if 1  // 改为1可执行此测试块
    int x = 10, y = 100;
    cout << "x=" << x << ",y=" << y << endl;
    // swap_val(&x, &y);  // 指针版交换
    swap_val2(x, y);     // 引用版交换
    cout << "x=" << x << ",y=" << y << endl;
#endif
}

// 测试const引用(万能引用)
void test_const_ref() {
#if 1  // 改为1可执行此测试块
    // const 引用:"万能引用"
    const int& len = 1;  // 1. 初始化值为常数

    int x = 10;
    const int& xr = x;   // 2. 初始化值为 变量

    const int len2 = 20;
    const int& len2_r = len2; //3. 初始值为 const变量

    // int& y = len2_r;  //error, 普通引用是无法初始值为const引用(常数)
#endif
}

// 测试内联函数
//内联函数优点:效率高,用空间换时间。
//内联函数的特点:体积小,结构简单
void test_inline() {
#if 1  // 改为1可执行此测试块
    // 调用add函数时,系统做了那些事? 1.保存现场(因为函数调用是在主函数内进行的,我们要去其他函数去处理问题时,要跳出主函数,就相当于我们工作出差,出差的过程要把家门锁好,免得有坏人来破坏,所以,锁门的动作就相当于保护现场) 
    // 2.将函数入栈 3. 以先右后左方向将实参入栈(大多数函数是)  4. 调用函数功能并获取结果 5.恢复现场,处理结果, 同时回收上一次函数的栈空间
    // add是inline函数,则会直接展开
    // inline函数的展开,即将函数体的功能直接拿出来使用,什么意思呢,就是将这个函数的实现内容及代码直接搬到主函数内调用这个函数的地方,也叫做内嵌,或者叫做内联函数的展开,不需要传递参数,相当于将我们出差干的事情搬到加里面去干了,速度更快。
    // inline函数在调用时,不会像普通函数那样经过创建函数空间、传递参数...等复杂的流程
    int ret = add(10, 20);
    cout << ret << endl;
    //因为add是内联函数,就相当于cout<< 10+20<<endl;不需要传参,直接将10和20进行add函数内部的a + b操作(函数内部的实现内容)。
    int ret2 = sub(30, 5);
    cout << ret2 << endl;

    // 内联函数设计时: 1)体积小  2) 逻辑简单(不带复杂的循环)

    // 内联函数与有参宏的区别?  【面试题】
    // 1) 有参宏是预处理时替换(展开的), 而内联函数是编译时展开
    // 2) 有参宏是直接文本替换(无脑),而内联函数具有类型或语法检查(有脑)
#endif
}

// 主函数:统一调用各个测试函数
int main()
{
    // 按需开启测试(修改对应函数内的#if 1/#if 0)
    test_basic_syntax();  // 基础语法测试
    test_swap();          // 交换函数测试
    test_const_ref();     // const引用测试
    test_inline();        // 内联函数测试

    return 0;
}
#endif
相关推荐
旖-旎2 小时前
哈希表(字母异位次分组)(5)
数据结构·c++·算法·leetcode·哈希算法·散列表
XiYang-DING2 小时前
【Java】二叉搜索树(BST)
java·开发语言·python
Lyyaoo.2 小时前
【JAVA基础面经】进程安全问题(synchronized and volatile)
java·开发语言·jvm
Andya_net2 小时前
Java | 基于 Feign 流式传输操作SFTP文件传输
java·开发语言·spring boot
无限进步_2 小时前
【C++】多重继承中的虚表布局分析:D类对象为何有两个虚表?
开发语言·c++·ide·windows·git·算法·visual studio
清水白石0082 小时前
向后兼容的工程伦理:Python 开发中“优雅重构”与“责任担当”的平衡之道
开发语言·python·重构
A.A呐2 小时前
【QT第六章】界面优化
开发语言·qt
小夏子_riotous2 小时前
openstack的使用——5. Swift服务的基本使用
linux·运维·开发语言·分布式·云计算·openstack·swift
千码君20162 小时前
kotlin:Jetpack Compose 给APP添加声音(点击音效/背景音乐)
android·开发语言·kotlin·音效·jetpack compose