一 、C++的第一个程序
C++兼容C语⾔绝⼤多数的语法,所以C语⾔实现的hello world依旧可以运⾏,C++中需要把定义⽂件代码后缀改为.cpp,vs编译器看到是.cpp就会调⽤C++编译器编译

C语言实现hello world

C++语言实现hello world

二、命名空间namespace
1.namespace的价值
namespace(命名空间)是 C++ 用来解决命名冲突、组织代码的核心机制,简单说就是给变量、函数、类等起一个 "专属姓氏",避免不同模块的同名标识符互相干扰
例如:
c
#include <stdio.h>
int rand = 0;
int main()
{
printf("%d\n", rand);
return 0;
}
// 这个代码可以正常运行
// 但是如果加了头文件 <stdlib.h>就会报错
c
#include <stdio.h>
#include <stdlib.h>
int rand = 0;
int main()
{
// 编译报错:error C2365: "rand": 重定义;以前的定义是"函数"
printf("%d\n", rand);
return 0;
}
c语⾔项⽬类似下⾯程序这样的命名冲突是普遍存在的问题,C++引⼊namespace就是为了更好的解决这样的问题。
namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下
⾯的rand不在冲突了
c
#include <stdio.h>
#include <stdlib.h>
namespace R
{
int rand = 0;
}
int main()
{
R::rand = 10;
printf("%d\n", rand);
return 0;
}
2.namespace的定义,使用方法
(1) 定义命名空间
需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等
c
// A是命名空间的名字,⼀般开发中是⽤项⽬名字做命名空间名。
namespace A
{
int rand = 0; //变量 全局变量必须初始化(避免未定义行为)
int Add(int right, int left) //函数
{
return right + left;
}
struct Node //类型
{
struct Node* next;
int val;
};
}
(2)使用namespace的三种方法
方式1:作用域解析符 ::(推荐,最安全)
明确指定使用哪个命名空间的成员,不会引发命名冲突
c
int main()
{
// 调用成员时需要使用(否则编译警告)
A::rand = 10; // 使用变量:赋值
int sum = A::Add(2, 3); // 使用函数:调用并传参
A::Node node; // 使用结构体:定义对象
return 0;
}
方式 2:using 声明(单独引入一个成员)
只引入需要的成员,精简代码
c
// 把 A 里的 Add 函数引入当前作用域,可直接使用函数名调用
using A::Add;
int main()
{
// 调用 Add 函数计算 1+1,结果存入 sum
int sum = Add(1,1);
return 0;
}
(3)方式 3:using 指令(引入整个命名空间)
一次性引入所有成员,新手常用,但大型项目不推荐(容易引发冲突)
c
// 引入整个命名空间 A
// 作用:可以直接使用 A 中的所有成员,不需要写 A::
using namespace A;
// 主函数:程序入口
int main()
{
// 给命名空间 A 中的全局变量 rand 赋值为 10
rand = 10;
//引用了头文件<iostream>这里会报错
//<iostream> 会间接引入标准库,rand 已经被占用了,你自己定义的 int rand 和它重名冲突了!
// 调用 A 中的 Add 函数,计算 1 + 2,结果存入 sum(sum = 3)
int sum = Add(1, 2);
// 定义一个 Node 类型的结构体变量 node
Node node;
// 程序正常结束,返回 0
return 0;
}
(4)域隔离
C++中域有函数局部域,全局域,命名空间域,类域 ;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所以有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的⽣命周期 ,命名空间域和类域不影响变量⽣命周期
简记:
域 = 名字的家
局部域 & 全局域 = 既给名字安家,又管寿命
命名空间域 & 类域 = 只给名字安家,不管寿命
(5) namespace只能定义在全局,当然他还可以嵌套定义
正确定义
c
#include <iostream>
//定义在全局
namespace a
{
int rand = 0;
}
//嵌套定义
namespace B
{
int rand = 0;
namespace C
{
int Add(int right, int left)
{
return right + left;
}
}
}
错误定义
定义在代码块内部--报错

定义在函数--报错

定义在类内部--报错

namespace 的目的是做全局级别的名字隔离 ,
它是给编译器、链接器看的,不是给运行时看的。
所以 C++ 规定:
namespace 必须在编译期就能完全确定位置,不能放在运行时才生效的区域
(6)项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace
同名命名空间,无论在多少个文件中定义,都视为同一个空间,自动合并,不冲突。
例如:
文件A.h
c
//文件A.h
namespace Project
{
// 声明函数 funcA
// 作用:提供接口声明,具体实现写在 .cpp 文件中
void funcA();
}
文件A.cpp
c
//文件A.cpp
#include "A.h"
namespace Project
{
void funcA()
{
// 这里写函数功能
}
}
文件B.h
c
//文件B.h
namespace Project
{
// 声明函数 funcB
// 作用:提供接口声明,具体实现写在 .cpp 文件中
void funcB();
}
文件B.cpp
c
//文件B.cpp
#include "B.h"
namespace Project
{
void funcB()
{
// 这里写函数功能
}
}
最终编译器会把所有文件里的 Project 合并,你可以在任何地方统一使用
c
namespace Project
{
void funcA();
void funcB();
}
(7)标准库std
所有 C++ 标准库(cout, cin, string, vector, map, algorithm...)全部都被封装在一个名字叫 std 的命名空间里。
推荐写法
c
// 正确写法(推荐)
std::cout << "hello";
std::string name = "小明";
std::vector<int> vec;
把std整个打开,但工程上不推荐,因为打开了整个标准库,容易冲突。
c
// 把 std 整个打开,所有东西直接用
using namespace std;
cout << "hello";
string name;
vector<int> vec;
二、C++的输入和输出
<iostream>是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象std::cin是 istream 类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输⼊流std::cout是 ostream 类的对象,它主要⾯向窄字符的标准输出流std::endl是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区<<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们,即有<iostream>+ 有std::才能正常使用他们
演示cout
c
// 包含 C++ 标准输入输出流头文件
// 用于使用 std::cout 进行控制台打印
#include <iostream>
// 程序入口主函数
int main()
{
// 定义整型变量 a,赋值 3
int a = 3;
// 定义双精度浮点型变量 b,赋值 1.1
double b = 1.1;
// 定义字符型变量 c,赋值字符 'x'
char c = 'x';
// 控制台输出:打印 a b c,中间用空格隔开,最后换行
std::cout << a << " " << b << " " << c << std::endl;
// 程序正常结束
return 0;
}

演示cin
c
// 必须包含输入输出流头文件
#include <iostream>
// 主函数:程序入口
int main()
{
// 定义三个变量,用于存储输入的数据
int a; // 整型变量,存储整数
double b; // 浮点型变量,存储小数
char c; // 字符型变量,存储单个字符
// 控制台提示文字:告诉用户需要输入内容
std::cout << "输入:";
// 接收用户输入,依次赋值给 a、b、c
// 输入时用 空格 / 回车 分隔即可
std::cin >> a >> b >> c;
// 输出最终结果,带提示文字
std::cout << "输出:" << a << " " << b << " " << c << std::endl;
// 程序正常结束
return 0;
}

- 使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输⼊输出可以⾃动识别变量类型(),其实最重要的是C++的流能更好的⽀持⾃定义类型对象的输⼊输出
c
// 主函数:程序入口
int main()
{
// 定义三个变量,用于存储输入的数据
int a; // 整型变量
double b; // 双精度浮点型变量
char c; // 字符型变量
/* ======================================
C 语言风格输入:scanf
特点:
1. 必须手动写【占位符】%d、%lf、%c,指定变量类型
2. 变量前面必须加 & 符号(取地址)
3. 格式写错(比如把double写成%d)会直接崩溃/乱码
4. 语法繁琐,容易出错
====================================== */
scanf("%d %lf %c", &a, &b, &c);
/* ======================================
C 语言风格输出:printf
特点:
1. 必须手动写【占位符】%d、%lf、%c
2. 不能自动识别类型,必须人来匹配
3. 格式写错会输出乱码
====================================== */
printf("%d %lf %c", a, b, c);
/* ======================================
对比:C++ 风格输入:cin
特点:
1. 不需要写任何占位符!自动识别变量类型
2. 变量前面 不需要 & 符号
3. 语法简单,安全,不易出错
cin >> a >> b >> c;
====================================== */
/* ======================================
对比:C++ 风格输出:cout
特点:
1. 不需要写任何占位符!自动识别类型
2. 直接写变量名即可
3. 支持自定义对象输出,功能更强
cout << a << " " << b << " " << c << endl;
====================================== */
// 程序正常结束
return 0;
}
三、缺省参数
- 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数
c
#include <iostream>
// 缺省参数:声明或定义函数时为函数的参数指定一个缺省值
// 全缺省参数:函数的全部形参都指定了缺省值
// 此处 right 缺省值为 1,left 缺省值为 1
int Add(int right = 1, int left = 1)
{
return right + left;
}
int main()
{
// 调用函数时没有指定实参,采用形参的缺省值 1 + 1,输出结果 2
std::cout << Add() << std::endl;
// 调用函数时指定了实参,使用指定的实参 2 + 3,输出结果 5
std::cout << Add(2, 3) << std::endl;
return 0;
}
- 全缺省就是全部形参给缺省值
c
//这就是全缺省,right和left都给了缺省值
int Add(int right = 1 ,int left = 1)
{
return right + left;
}
- 半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值
c
//例如函数
// 半缺省参数:部分参数指定缺省值,部分参数必须传入实参
// 缺省参数规则:缺省值必须从右往左连续给出
//正确写法:
// year 无缺省值,必须传参;month 缺省值=2,day 缺省值=1
void Date(int year, int month = 2, int day = 1)
{
// 按照 年/月/日 格式打印输出
std::cout << year << "/" << month << "/" << day << std::endl;
}
// year 无缺省值,必须传参;month 无缺省值,必须传参;day 缺省值=1
void Date(int year, int month, int day = 1)
{
// 按照 年/月/日 格式打印输出
std::cout << year << "/" << month << "/" << day << std::endl;
}
//错误写法
//错误位置:缺省参数必须 从右往左 连续设置,不能跳过中间参数
void Date(int year = 2026, int month, int day = 1)
{
std::cout << year << "/" << month << "/" << day << std::endl;
}
- 带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参
c
#include <iostream>
// 全缺省参数:所有参数都指定了缺省值
void Date(int year = 2026, int month = 1, int day = 1)
{
std::cout << year << "/" << month << "/" << day << std::endl;
}
int main()
{
// 合法调用
// 带缺省参数的函数调用,C++规定必须从左到右依次给实参
Date(2026); // 传1个参数:给year
Date(2026, 2); // 传2个参数:给year、month
Date(2026, 2, 2); // 传3个参数:给year、month、day
// 非法调用
// 违反规则:不能跳跃给实参,必须从左到右依次传参
Date(,,2026); // 错误:跳过year、month,只给day
Date(2026,,2); // 错误:跳过month,只给year、day
Date(,,2); // 错误:跳过year、month,只给day
return 0;
}
5.函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
c
// 文件 A.h
void func(int a, int b = 0); // 头文件已经写了缺省值 b=0
// 文件 test.cpp
#include "A.h"
#include <iostream>
// 错误:定义时又写了 b = 0
void func(int a, int b = 0)
{
}
//vs编译器 C2572 "func": 重定义默认参数
四、函数重载
C++⽀持在同⼀作⽤域 中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同
函数重载3条铁律:
- 必须在同一个作用域里
不同命名空间 / 类里不算重载。
c
// 同一个作用域(全局域)算重载
#include <iostream>
// 同一个全局作用域
void print(int a)
{
std::cout << "int: " << a << std::endl;
}
// 同名、同域、参数不同 → 重载
void print(double a)
{
std::cout << "double: " << a << std::endl;
}
int main() {
print(10); // 调用 int 版本
print(3.14); // 调用 double 版本
return 0;
}
c
//不同命名空间不算重载!
//作用域不同,只是名字碰巧一样而已!
//调用时必须写:
A::print(10);
B::print(3.14);
#include <iostream>
namespace A
{
void print(int a) //一个在 A 域
{
}
}
namespace B
{
void print(double a) //一个在 B 域
{
}
}
c
//不同类里 不算重载!
//它们属于不同类的域,只是同名函数,不是重载
class A {
public:
void print(int a)
{
}
};
class B {
public:
void print(double a)
{
}
};
- 必须满足:参数不同
类型不同
个数不同
顺序不同(类型顺序)
c
// 重载:参数类型不同
void func(int a)
{
}
void func(double a)
{
}
// 重载:参数个数不同
void func(int a, int b)
{
}
// 重载:参数类型顺序不同
void func(int a, double b)
{
}
void func(double a, int b)
{
}
- 以下情况 不能构成重载
返回值不同!
只有缺省参数不同
只有参数名不同
c
// 错误:只有返回值不同,不算重载
int func(int a)
{
}
void func(int a)
{
}
//错误:只有缺省参数不同
//报错!编译器认为是重复定义!
int func(int a = 1)
{
}
int func(int a = 2)
{
}
//错误:只有参数名不同,不算
void func(int a)
{
}
void func(int b)
{
}
五、引用
1.引用的概念和定义
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。
引⽤和取地址使⽤了同⼀个符号&
c
#include <iostream>
int main()
{
// 定义整型变量 a,初始值为 0
int a = 0;
// 定义引用 ra,将 ra 作为变量 a 的别名(引用本质是变量的别名)
int& ra = a;
// 定义引用 rb,将 rb 作为引用 ra 的别名,最终还是 a 的别名
int& rb = ra;
// 定义引用 rc,将 rc 作为引用 rb 的别名,最终同样是 a 的别名
int& rc = rb;
// 打印变量 a 的地址
std::cout << &a << std::endl;
// 打印引用 ra 的地址(与 a 地址相同,因为 ra 是 a 的别名)
std::cout << &ra << std::endl;
// 打印引用 rb 的地址(与 a 地址相同,共用同一块内存空间)
std::cout << &rb << std::endl;
// 打印引用 rc 的地址(与 a 地址相同,所有引用都指向同一块内存)
std::cout << &rc << std::endl;
return 0;
}

2.引用的特性
- 引用在定义时必须初始化
别名必须依附于一个已经存在的变量,不能凭空存在。就像:你不能先给一个人起外号,却没有这个人。
c
//正确写法
int a = 0;
//定义 + 初始化(绑定a)
int& ra = a;
c
//错误写法
int& ra; // 错误!引用没有初始化
ra = a; // 太晚了!
- ⼀个变量可以有多个引⽤
一个变量可以有很多个别名(引用),大家共用同一块内存。
c
#include <iostream>
int main()
{
// 定义整型变量 a,开辟一块内存空间,值为 0
int a = 0;
// 引用的本质:给变量起别名,不开辟新内存
// 一个变量可以同时拥有多个引用(别名)
int& ra1 = a; // ra1 是 a 的引用(别名)
int& ra2 = a; // ra2 是 a 的引用(别名)
int& ra3 = a; // ra3 是 a 的引用(别名)
// 打印变量与引用的值:所有引用的值都等于原变量 a 的值
std::cout << a << std::endl; // 输出 a 的值
std::cout << ra1 << std::endl; // 输出 ra1 代表的值(即 a)
std::cout << ra2 << std::endl; // 输出 ra2 代表的值(即 a)
std::cout << ra3 << std::endl; // 输出 ra3 代表的值(即 a)
// 打印变量与引用的地址:引用与原变量共用同一块内存,地址完全相同
std::cout << &a << std::endl; // a 的地址
std::cout << &ra1 << std::endl; // ra1 的地址(和 a 一样)
std::cout << &ra2 << std::endl; // ra2 的地址(和 a 一样)
std::cout << &ra3 << std::endl; // ra3 的地址(和 a 一样)
return 0;
}

- 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
例1
c
#include <iostream> // 补充必要头文件,确保cout等输入输出正常使用
int main()
{
// 定义整型变量a,初始值设为0,用于存储基础数值
int a = 0;
// 定义整型变量b,初始值设为3,作为对比数值
int b = 3;
// 定义引用ra1,将其绑定到变量a,ra1成为a的别名,与a共用同一块内存
int& ra1 = a;
// 错误:同一作用域内,引用变量ra1已绑定到a,无法重复定义,且引用一旦绑定变量,不能重新绑定到其他变量(如b)
int& ra1 = b;
// 程序正常结束,返回0
return 0;
}

例2
c
#include <iostream> // 引入输入输出头文件,确保cout等功能正常使用
int main()
{
// 定义整型变量a,初始值设为0,用于后续引用绑定
int a = 0;
// 定义整型变量b,初始值设为3,作为后续赋值的数据源
int b = 3;
// 定义引用ra1,绑定到变量a,ra1成为a的别名,与a共用同一块内存
int& ra1 = a;
// 打印引用ra1的值(本质是打印a的值),输出0
std::cout << ra1 << std::endl;
// 关键:此处不是修改引用的绑定关系,而是给引用ra1赋值(即给a赋值)
// 引用一旦绑定变量,无法更改绑定对象,此处实际是修改a的值,间接修改ra1的值
ra1 = b;
// 打印修改后ra1的值(即a的值),输出3
std::cout << ra1 << std::endl;
return 0;
}

3.引用的使用
- 引⽤在实践中主要是于引⽤传参 和引⽤做返回值,减少拷⻉提⾼效率
引用传参
c
#include <iostream> // 补充必要头文件,否则cout无法使用
// 交换函数:采用引用传参,避免值拷贝,提高效率且能直接修改原变量
// 形参a、b均为int类型引用,分别绑定调用时传入的实参,操作引用即操作原实参
void swap(int& a, int& b)
{
int tmp = a; // 定义临时变量tmp,存储引用a对应的原实参值
a = b; // 将引用b对应的实参值,赋值给引用a,同步修改原实参
b = tmp; // 将临时变量tmp的值(原a的实参值),赋值给引用b,同步修改原实参
}
int main()
{
int a = 1; // 定义原变量a,初始值1
int b = 2; // 定义原变量b,初始值2
// 打印交换前的变量值,直观展示交换前状态
std::cout << "交换前:" << "a = " << a << " " << "b = " << b << std::endl;
swap(a, b); // 调用交换函数,传入a、b的引用,直接操作原变量
// 打印交换后的变量值,验证交换效果
std::cout << "交换后:" << "a = " << a << " " << "b = " << b << std::endl;
return 0;
}

引用做返回值
返回的变量,必须活得比函数久 (全局变量 /static 局部变量 / 引用传进来的变量)
绝对不能返回普通局部变量的引用(函数结束就销毁,引用会悬空)
例子 1:返回静态局部变量
c
#include <iostream>
// 引用做返回值:返回变量的别名,不产生拷贝,提高效率
// 可以通过返回的引用直接修改原变量本体
int& func()
{
// static 静态局部变量:存储在全局区,生命周期贯穿整个程序
// static 变量 只初始化 一次
// 函数调用结束后不会销毁,因此可以安全返回它的引用
static int a = 10;
// 返回静态变量a的引用(别名),不是拷贝值
return a;
}
int main()
{
// 引用做返回值的核心用法:
// func()返回a的引用,可直接对引用赋值,等价于修改原变量a
func() = 100;
// 再次调用func(),返回的还是同一个静态变量a的引用,输出修改后的值100
std::cout << func();
return 0;
}
例子 2:返回全局变量
c
#include <iostream>
// 全局变量:生命周期贯穿整个程序,程序运行期间一直存在,不会销毁
int a = 10;
// 引用做返回值:返回全局变量a的引用,无拷贝,效率高
int& func()
{
return a; // 正确:全局变量不会销毁,可安全返回引用
}
int main()
{
func() = 100; // 通过返回的引用修改全局变量a
std::cout << func(); // 输出修改后的值 100
return 0;
}
4.const 引用
const 引用(常量引用)是 C++ 中绑定到常量对象 / 值、且不允许通过该引用修改所绑定对象的引用类型,核心是只读引用。
语法
c
const 数据类型& 引用名 = 目标对象;
1.const 引用只能读,不能写
c
#include <iostream>
int main()
{
int a = 10; // 定义普通变量a
int& ra1 = a; // 普通引用:可读可写
ra1 = 100; // 合法:普通引用可以修改原变量a的值
const int& ra2 = a; // 常引用:被const修饰,权限缩小为只读,不能修改
//ra2 = 100; // 错误:常引用不能修改值,编译报错
return 0;
}

2. const 引用可以引用临时值 / 常量
临时值(字面量)没有内存地址,普通引用不能绑定,但 const 引用可以延长临时值的生命周期,让它安全存在
c
#include <iostream>
int main()
{
// 错误:普通引用 不能 引用 常量(字面量)
// 原因:非常量引用必须引用一块可修改的内存空间,10是常量,没有内存地址
//int& a = 10;
// 正确:const 引用 可以 引用 常量(字面量)
// 原因:常引用权限只读,编译器会自动创建临时变量存储10,让a1绑定临时变量
const int& a1 = 10;
return 0;
}
- 最常用场景:函数传参(保护数据 + 提高效率)
c
#include <iostream>
// const 引用做函数参数
// 作用:1. 不拷贝数据,提高效率 2. 保护实参不被修改
void print(const int& x)
{
//x = 100; //错误:const 引用只读,不能修改,保护数据
std::cout << x;
}
int main() {
int a = 100;
print(a); // 可以传普通变量
print(200); // 可以传常量(const引用支持绑定临时常量)
return 0;
}
5.指针与引用的关系
| 引用 | 指针 | |
|---|---|---|
| 本质 | 变量别名 | 存地址的变量 |
| 空间 | 不开空间 | 开辟空间 |
| 初始化 | 必须初始化 | 可以不初始化 |
| 指向 | 不能改 | 可以改 |
| 访问 | 直接用 | 需要解引用 |
| sizeof | 变量本身大小 | 地址大小 |
| 空值 | 不存在空引用 | 可以是空指针NULL |
| 安全性 | 更安全 | 较危险 |
六、inline
1. 作用和语法
当我们写完一个函数并且调用它的时候有以下执行过程:保存当前位置、跳转到函数、传参、执行计算、跳回 main、继续执行,这些步骤叫做函数调用开销 ,函数体越小,开销占比越大,非常不划算。
inline = 内联函数
作用:告诉编译器这个函数很小,调用的时候别跳转,直接把代码粘贴过来执行
语法:只需要在函数前面加 inline 即可
例如
cpp
#include <iostream>
// inline 关键字:建议编译器将此函数作为【内联函数】处理
// 作用:编译时直接将函数体展开到调用处,**省去函数调用开销**,提高效率
// 适合:逻辑简单、代码短小、频繁调用的函数(如加减、取值)
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
// 调用内联函数 Add
// 编译器会直接替换成:int c = 1 + 2; 不产生函数调用
int c = Add(1, 2);
return 0;
}
2.特点
- 只适合短小函数
inline只是建议,不是命令。你写了 inline,编译器可以拒绝:函数太长 → 不展开,递归函数 → 不展开,复杂逻辑 → 不展开- 空间换时间,速度变快可执行程序变大(代码被复制多份)
- 声明和定义建议放在一起,inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错
3.如何在VS上观察inilne修饰的函数是否展开
vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下以下两个地⽅。
- 解决方案右键打开
- 找到属性,接下来的看图


- 可以通过汇编观察程序是否展, 有
call就是没有展开,没有call就是展开了(在你想看汇编的代码行按F9打个断点,暗F5启动调试,程序停在断点处,按快捷键Ctrl + Alt + D)
没call,展开了

有call,没展开

4.inline与宏的对比
- 如何用宏定义一个函数
基本语法
cpp
#define 宏名(参数列表) 替换文本
//例如 Add函数
#define Add(a,b) (a)+(b)
注意 :
a.每个参数都要加括号
cpp
// 错误
#define SQR(x) x*x
int a = SQR(1+2); // 变成 1+2*1+2 = 5,不是 9
// 正确
#define SQR(x) ((x)*(x))
b.整体也要加括号,避免运算优先级出问题
c.宏没有类型检查,不会传值,只是替换
- inline 与 宏的对比
| 特性 | inline内联函数 | 宏(#define) |
|---|---|---|
| 处理时机 | 编译阶段 | 预处理阶段 |
| 类型检查 | 严格检查 | 不检查 |
| 语法安全 | 安全 | 容易出错 |
| 调试 | 可调式 | 难调试 |
七、nullptr
nullptr 是 C++11 标准引入的空指针常量,专门用来表示指针不指向任何有效内存地址
1.作用
a.明确表示空指针 ,语义清晰
b.解决了 C 语言中 NULL 的类型缺陷问题
c.所有指针类型都可以用它初始化 / 赋值
2.基础用法
a.定义空指针
b.判断指针是否为空
c.清空已有指针
cpp
// 1. 定义空指针
int* p1 = nullptr;
char* p2 = nullptr;
std::string* p3 = nullptr;
// 2. 判断指针是否为空(推荐写法)
if (p1 == nullptr)
{
// 指针为空,执行逻辑
}
// 3. 清空已有指针
p1 = nullptr;
3.为什么不用NULL?
C语言中NULL本质上是宏定义 ,是整数0 ,不是
真正的指针类型
cpp
#define NULL 0
容易引发歧义
cpp
void func(int num)
{
cout << "整数版本" << endl;
}
void func(int* p)
{
cout << "指针版本" << endl;
}
// 调用:本意想调用指针版本,结果调用了整数版本!
func(NULL);
4.关键特性
a.类型:std::nullptr_t,可以隐式转换为任意指针类型;
b.不能转换为整数:int a = nullptr; 编译报错;
c.唯一值:所有 nullptr 都相等;
d.安全性:解引用空指针会崩溃,但 nullptr 比 NULL 更易排查问题。