C++入门

一 、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++的输入和输出

  1. <iostream> 是 Input Output Stream 的缩写,是标准的输⼊、输出流库,定义了标准的输⼊、输出对象
  2. std::cin 是 istream 类的对象,它主要⾯向窄字符(narrow characters (of type char))的标准输⼊流
  3. std::cout 是 ostream 类的对象,它主要⾯向窄字符的标准输出流
  4. std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区
  5. <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移
  6. 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;
}
  1. 使⽤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;
}

三、缺省参数

  1. 缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数
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;
}
  1. 全缺省就是全部形参给缺省值
c 复制代码
//这就是全缺省,right和left都给了缺省值
int Add(int right = 1 ,int left = 1)
{
	return right + left;
}
  1. 半缺省就是部分形参给缺省值。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;
}
  1. 带缺省参数的函数调⽤,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条铁律:

  1. 必须在同一个作用域里
    不同命名空间 / 类里不算重载。
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) 
    {
    
    }
};
  1. 必须满足:参数不同
    类型不同
    个数不同
    顺序不同(类型顺序)
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) 
{

}
  1. 以下情况 不能构成重载
    返回值不同!
    只有缺省参数不同
    只有参数名不同
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.引用的特性

  1. 引用在定义时必须初始化
    别名必须依附于一个已经存在的变量,不能凭空存在。就像:你不能先给一个人起外号,却没有这个人。
c 复制代码
//正确写法
int a = 0;
//定义 + 初始化(绑定a)
int& ra = a;
c 复制代码
//错误写法
int& ra;  // 错误!引用没有初始化
ra = a;   // 太晚了!
  1. ⼀个变量可以有多个引⽤
    一个变量可以有很多个别名(引用),大家共用同一块内存。
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. 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体

例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.引用的使用

  1. 引⽤在实践中主要是于引⽤传参引⽤做返回值,减少拷⻉提⾼效率

引用传参

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;
}
  1. 最常用场景:函数传参(保护数据 + 提高效率)
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.特点

  1. 只适合短小函数
  2. inline只是建议,不是命令。你写了 inline,编译器可以拒绝:函数太长 → 不展开,递归函数 → 不展开,复杂逻辑 → 不展开
  3. 空间换时间,速度变快可执行程序变大(代码被复制多份)
  4. 声明和定义建议放在一起,inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错

3.如何在VS上观察inilne修饰的函数是否展开

vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下以下两个地⽅。

  1. 解决方案右键打开
  2. 找到属性,接下来的看图
  1. 可以通过汇编观察程序是否展, 有call就是没有展开,没有call就是展开了(在你想看汇编的代码行按F9打个断点,暗F5启动调试,程序停在断点处,按快捷键Ctrl + Alt + D

call,展开了

call,没展开

4.inline与宏的对比

  1. 如何用宏定义一个函数

基本语法

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.宏没有类型检查,不会传值,只是替换

  1. 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.安全性:解引用空指针会崩溃,但 nullptrNULL 更易排查问题。

相关推荐
江湖中的阿龙2 小时前
深入理解 CAS:Java 无锁并发核心原理、缺陷与应用场景详解
java·开发语言
繁星星繁2 小时前
Python基础语法(一)
c++·笔记·python
进击的荆棘2 小时前
C++起始之路——继承
开发语言·c++
格林威2 小时前
工业相机图像采集处理:从 RAW 数据到 AI 可读图像,堡盟相机 C#实战代码深度解析
c++·人工智能·数码相机·opencv·算法·计算机视觉·c#
NGC_66112 小时前
深入解析 ConcurrentHashMap 设计思想:高并发下的线程安全哈希表
java·开发语言
子夜四时歌3 小时前
Python详细安装与环境搭建
开发语言·python
Jinkxs3 小时前
SkyWalking - Python 应用追踪:基于 skywalking-python 的埋点
开发语言·python·skywalking
大头博士先生3 小时前
【3月考】二级Python最新真题及满分代码合集(基本操作题部分)
开发语言·python
xcLeigh3 小时前
IoTDB Python原生接口全攻略:从基础读写到高级实战
开发语言·数据库·python·api·iotdb·原生接口·读写数据