CPP(二)引用、指针、模板、强转、内联函数

引用

引用的本质 —— 变量的别名

C++ 中,引用是一个变量的别名,一旦引用某个变量,操作引用就等于操作原变量。

语法
int a = 10;
int& ref = a;  // ref 是 a 的引用,ref 与 a 是同一块内存
ref = 20;      // 实际修改的是 a
  • 引用必须初始化,不能为 null

  • 一旦初始化,就不能再引用别的变量

  • 不是拷贝,而是同一个实体

 函数参数传递方式

 值传递

拷贝一份实参数据传给形参

修改形参不会影响原始变量

void foo(int x) { x = 100; } // 修改的是副本

 地址传递

传入变量地址(指针),通过 *ptr 修改原数据

void foo(int* x) { *x = 100; } // 修改的是实参

引用传递

直接传入别名,不拷贝、不取地址,语法简单

void foo(int& x) { x = 100; }  // 修改的是实参

引用使用时的注意事项

1.引用必须初始化

int& r; // ❌ 错误:引用必须初始化

原因:引用不是对象,而是某个对象的别名,它必须在创建时绑定一块已有的内存。

 2.引用一旦绑定不能更改绑定对象

int a = 10, b = 20;
int& r = a;
r = b;   // ❌ 不是让 r 改为引用 b,而是把 b 的值赋给 a

因为 C++ 中引用在底层编译时就是一个隐式的指针常量(int* const),一旦初始化,就不能改变所指对象。

int& r = a;
// 本质:int* const r = &a; // const pointer,不能再指向 b

const

const 修饰的变量/对象,其值不可修改,也就是说它是只读的,编译器会强制限制赋值操作。

 在底层,const 其实是编译期的保护机制,不影响变量的存储结构,但会限制你通过该名字修改数据的能力

语法及用法

1️⃣ 基本用法(定义常量):

const int a = 10;   // 定义常量 a,不能修改
// a = 20;  // ❌ 错误:const 变量不可修改

 2️⃣ const int v VS int const v 

没有区别,两种写法是等价的,只是顺序不同

const int a = 10;   // 表示 a 是一个 const 的 int
int const b = 20;   // 同样表示 b 是一个 const 的 int

const 修饰指针的三种情况

① 指向常量的指针(不能改值,可以改地址)

const int* p = &a;  // 或写作:int const* p = &a;
*p = 20;     // ❌ 错误,不能修改所指内容
p = &b;      // ✅ 可以修改指针本身

📌 含义:*pconst int,不能通过 p 修改它指向的值。

 ② 常量指针(可以改值,不能改地址)

int* const p = &a;
*p = 20;     // ✅ 可以修改内容
p = &b;      // ❌ 错误,不能改变 p 的指向

📌 含义:p 是 const 的,不能改指向,但 *p 是普通 int,可以改值。

 ③ 指向常量的常量指针(双重 const)

const int* const p = &a;
*p = 20;     // ❌ 不行
p = &b;      // ❌ 不行

📌 含义:p*p 都是只读的,完全不能动

 const 在函数中的应用

用途:防止函数修改实参;保证返回值只读;用于成员函数中表示不修改对象

void print(const string& str) {
    cout << str << endl;
    // str = "xx";  // ❌ 不允许
}

 const 修饰函数返回值

const int getValue() {
    return 42;
}

返回值不能被修改(意义不大,通常用于对象引用或指针)。

 成员函数 const 修饰(只读函数)

class Demo {
private:
    int data;
public:
    int get() const {   // 表示此函数不会修改成员变量
        return data;
    }
};

const vs #define 的区别

特性const#define
类型安全✅ 有类型检查❌ 无类型检查(纯文本替换)
作用域✅ 遵循作用域规则❌ 全局替换
可调试性✅ 可调试、有符号名❌ 只是字面值,调试困难
编译检查✅ 有❌ 无
内存占用可能分配空间(优化器处理)不占空间(文本替换)

C 与 C++ 中 const 的差异 

差异点C 语言C++
语法支持支持 const(C89 后)完全支持,语义更丰富
作用域支持函数内/外 const同上
类成员支持❌ 无类/对象✅ 可用于类成员、成员函数等
函数参数传递有时需手动加 const 避免修改支持引用传递、const修饰灵活

const 应用(CAN通信类封装) 

class CanMessage {
private:
    const uint8_t id;        // 固定ID,不可更改
    uint8_t data[8];

public:
    CanMessage(uint8_t msg_id) : id(msg_id) {}

    void setData(const uint8_t* buf, size_t len) {
        for (size_t i = 0; i < len && i < 8; ++i)
            data[i] = buf[i];  // 可以改 data,但不能改 id
    }

    uint8_t getId() const {
        return id;  // const 成员函数,不修改类
    }
};

const T、const T*、T *const、const T&、const T*& 的区别

写法中文含义
const T常量类型 T,值不可改
const T*指向常量 T 的指针
T *const常量指针,指针本身不可改
const T&对常量 T 的引用
const T*&指向常量 T 的指针的引用
T* const&常量指针的引用

1. const T

普通常量,值不可修改

const int a = 10;
// a = 20; // ❌ 错误:a 是常量

2. const T* ptr

或写作:T const* ptr。

👉 “指向常量 T 的指针”,你不能通过 ptr 修改所指内容,但可以改指针本身。

int a = 5;
const int* ptr = &a;  // 或 int const* ptr = &a;
*ptr = 10;   // ❌ 错误
ptr = nullptr;  // ✅ 可以改指针


[ptr] ---> [a: 5]
 ^   内容不能改

3. T* const ptr

👉 “常量指针”:指针本身不可变,但可以通过它改内容。

int a = 10;
int* const ptr = &a;

*ptr = 20;     // ✅
ptr = &b;      // ❌ 错误:指针不能改向

[ptr - 固定不动] ---> [a: 20]
           ^  但内容可改

4. const T& ref

👉 “常量引用”,即对常量的引用,不能通过引用修改绑定对象。

int a = 42;
const int& ref = a;

ref = 100;     // ❌ 错误:不能修改 a

📌 常量引用允许绑定临时值:

const int& r = 10;  // ✅ 合法,延长临时变量生命周期

5. const T*& ptrRef

👉 逐步解读:

  • const T*:指向常量的指针

  • const T*&这个指针本身是引用类型

📌 意思是:引用一个“指向常量的指针”,你可以改变它所指向的地址(换对象),但不能通过它修改对象值

int a = 5, b = 8;
const int* p = &a;
const int*& ref = p;  // ✅ ref 是 p 的别名

*ref = 10;    // ❌ 错误:ref 是指向 const 的指针,不能改值
ref = &b;     // ✅ 改变了 p 的指向

[ref] = alias of [p] ---> [a: 5]
                   ↘
                    [b: 8] 可重新绑定

6. T* const& ptrRef

👉 分析步骤:

  • T* const:常量指针(指针本身不能改)

  • T* const&:对“常量指针”的引用

📌 意思是:引用一个“指针固定不动”的指针,可以通过它修改值,但不能改它指向别的对象。

int a = 10, b = 20;
int* const p = &a;
int* const& ref = p;  // ref 是 p 的别名

*ref = 30;    // ✅ 改 a 的值
ref = &b;     // ❌ 错误:p 是 const 指针,不能改指向

[ref = p (固定)] ---> [a: 30]
         ^   无法换到 b

总结

写法中文意义可否改值可否改指向可否改指针本身
const T常量 T
const T*指向常量的指针
T* const常量指针(地址不能变)
const T&对常量 T 的引用
const T*&指向常量对象的指针的引用✅(改指向)
T* const&常量指针的引用

 强制转换

C语言风格的强制类型转换

📌 语法:目标类型)(表达式)

int a = 10;
float f = (float)a;  // 将 int 强制转换为 float

种写法 C 和 C++ 都支持,称为 C-style cast,简单直接,但无法明确语义和安全性

 C++ 新增四种强制类型转换

类型转换形式作用用法示例
static_cast基本类型转换、向上转型等static_cast<float>(x)
const_cast添加或移除 const 修饰const_cast<char*>(p)
reinterpret_cast重新解释底层字节(非常危险)reinterpret_cast<int*>(ptr)
dynamic_cast用于类的 RTTI(多态向下转型)dynamic_cast<Derived*>(basePtr)

1. static_cast<T>(expr)

  • 最常用,类型安全

  • 可用于:

    • 基本数据类型之间的转换(int → float

    • 指针的向上转换(子类 → 父类

int a = 42;
float f = static_cast<float>(a); // 安全转换

2. const_cast<T>(expr)

  • 用于 移除或添加 const 属性

  • 通常用于修改函数参数中的 const 指针(⚠️ 非常危险)

void modify(const int* p) {
    int* mod = const_cast<int*>(p);  // 移除 const,尝试修改
    *mod = 100;  // ⚠️ 如果原对象是 const 定义的,行为未定义!
}

3. reinterpret_cast<T>(expr)

  • 重新解释内存字节为另一种类型,最不安全的转换

  • 用于底层编程、设备驱动、内存操作

int a = 0x12345678;
char* p = reinterpret_cast<char*>(&a);  // 强制把 int* 变成 char*

4. dynamic_cast<T>(expr)

  • 用于多态类型之间的 安全向下转型

  • 必须有虚函数

  • 转换失败时返回 nullptr(指针)或抛异常(引用)

class Base { public: virtual void f() {} };
class Derived : public Base { };

Base* b = new Derived();
Derived* d = dynamic_cast<Derived*>(b);  // ✅ 安全向下转换

如果 b 实际不是 Derived*,则 d == nullptr

 内联函数

内联函数是使用 inline 关键字修饰的函数,它建议编译器在编译阶段将函数的代码展开到调用处,从而减少函数调用的开销(如压栈/跳转/返回)。

语法

inline void say_hello() {
    std::cout << "Hello!" << std::endl;
}

也可以写在类中,成员函数默认就被当作内联函数处理(如果函数体在类内定义)

class A {
public:
    void show() { std::cout << "show()" << std::endl; }  // 隐式 inline
};

内联的原理(底层机制)

函数正常调用(非 inline)

void f() { std::cout << "Hi"; }

void caller() {
    f();  // 运行时跳转 → 开销大(函数调用栈、返回地址等)
}

函数内联后

void caller() {
    std::cout << "Hi";  // 编译器在这里直接“拷贝粘贴”了 f() 的内容
}

inline 是“建议”不是命令

💡 编译器决定是否内联:

inline 只是告诉编译器 “我建议你内联这个函数”,最终是否展开代码,由编译器自己决定(优化器说了算)。

inline void foo();  // 只是建议,编译器可以忽略

📌 在现代 C++ 中,很多编译器(如 GCC/Clang)会自动根据函数规模、调用频率、编译优化选项(如 -O2)决定是否内联。

 函数重载

这是 C++ 为了解决函数命名冲突、提高接口友好度所引入的机制,它在面向对象、STL、泛型编程中非常常见。

在 C 语言中,函数名必须唯一。如果你有多个功能类似但参数不同的函数,就得起不同的名字:

void print_int(int a);
void print_float(float a);
void print_string(const char* str);

🧠 问题:函数名爆炸、管理困难、不直观!

在 C++ 中可以这样写

void print(int a);
void print(float a);
void print(const std::string& s);

✅ 函数名统一,接口更自然、易用、清晰。
✅ 编译器会根据参数个数、类型自动区分调用哪个版本。

语法

函数返回类型 函数名(参数列表);

👉 函数名相同,但参数个数或类型不同

#include <iostream>
using namespace std;

// 重载版本1:整型参数
void print(int a) {
    cout << "int: " << a << endl;
}

// 重载版本2:浮点型参数
void print(float a) {
    cout << "float: " << a << endl;
}

// 重载版本3:字符串参数
void print(const string& a) {
    cout << "string: " << a << endl;
}

int main() {
    print(10);           // 调用 int 版本
    print(3.14f);        // 调用 float 版本
    print("hello"s);     // 调用 string 版本(注意 "s" 是 C++14 字面量)
}

注意事项

❌ 1. 不能仅凭返回值重载

int func(int x);
float func(int x);  // ❌ 错误!与上面的函数只有返回类型不同

编译器在函数调用处只看参数列表,不看返回值 —— 因为返回值可以被忽略

🔍 所以不能这么做,否则会出现:func(10);  // 编译器不知道你是想要 int 还是 float 版本

✅ 2. 参数个数不同、类型不同可以重载

void sum(int a, int b);
void sum(int a, int b, int c);

✅ 3. const 修饰也能构成重载(特别是引用参数)

void display(string& s);
void display(const string& s);  // ✅ 合法重载

区分 TT&const T&

void set(int);         // 按值
void set(int&);        // 非 const 引用
void set(const int&);  // 常量引用

这些是三个不同的重载,使用时编译器根据实参来挑选最匹配的版本。

函数模板

基本语法

template<typename T>
T function_name(T a, T b) {
    // 泛型代码
}

T 是模板类型参数,可以代表任意类型(如 intfloatstd::string 等)

也可以使用 class 替代 typename,意义一样

template<class T>
T function_name(T a, T b);

示例:通用的最大值函数

#include <iostream>
using namespace std;

template<typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}

int main() {
    cout << max(3, 5) << endl;          // int
    cout << max(3.14, 2.71) << endl;    // double
    cout << max('a', 'z') << endl;      // char
}

📌 编译器在调用 max(a, b) 时会根据参数类型 自动推导 T 的类型并实例化函数版本

模板的工作原理

函数模板不是普通函数,而是生成函数的“蓝图”。只有在调用模板时,编译器才会:

  1. 推导参数类型

  2. 生成具体类型的函数(模板实例化)

max(3, 5);         // → 编译器生成 int max(int, int)
max(3.14, 2.71);   // → 生成 double max(double, double)

限制与注意事项

❌ 1. 类型必须支持模板中使用的操作

template<typename T>
T add(T a, T b) {
    return a + b;  // 要求 T 支持 + 运算符
}

若某个类型不支持 + 运算,调用时会编译失败。

✅ 2.支持函数重载与模板共存

template<typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int max(int a, int b, int c) {  // 普通函数重载
    return max(max(a, b), c);
}

📌 编译器优先匹配普通函数 → 再尝试模板函数。

🧠 3. 可以手动指定模板参数

max<int>(3, 7);  // 显式指定 T = int

⚠️ 4. 不同类型参数时,不能自动推导

max(3, 3.14);  // ❌ 编译错误:T 无法推导

✅ 解决方法

template<typename T1, typename T2>
auto max2(T1 a, T2 b) -> decltype(a > b ? a : b) {
    return (a > b) ? a : b;
}

或使用 C++17 的模板自动推导特性。

 函数默认参数

语法定义

void func(int param1, float param2 = 0.0f);

示例

#include <iostream>
using namespace std;

void greet(string name = "Guest", int times = 1) {
    for (int i = 0; i < times; ++i)
        cout << "Hello, " << name << "!" << endl;
}

int main() {
    greet();                     // 输出 Hello, Guest! ×1
    greet("Alice");             // 输出 Hello, Alice! ×1
    greet("Bob", 3);            // 输出 Hello, Bob! ×3
}

默认参数必须在声明处指定

默认参数通常写在函数声明(头文件)中,不建议写在定义中,否则可能重复定义或报错

// 头文件中
void log(string msg, int level = 1);

// 源文件中
void log(string msg, int level) {
    cout << "[L" << level << "] " << msg << endl;
}

✅ 推荐这样做:在声明处定义默认值,在定义处使用默认值就会引起冲突。

规则与注意事项 

1. 一旦某个参数设置默认值,后面所有参数必须也有默认值

// ❌ 错误写法
void func(int a = 1, int b);  // 编译错误:b 没有默认值

✅ 正确写法:
void func(int a, int b = 0);  // ✅ 合法

⚠️ 2. 调用时只能“从左往右”省略

void print(int a = 1, int b = 2, int c = 3);

// ✅ 合法
print();            // a=1, b=2, c=3
print(10);          // a=10, b=2, c=3
print(10, 20);      // a=10, b=20, c=3

// ❌ 错误:不能只跳过中间的 b
print(, , 30);      // 编译错误

🧠 必须连续从右往左省略参数

 3. 给已有默认值的参数传值时,左边参数都必须传

void foo(int x = 1, int y = 2);

// ✅ 合法
foo();        // x=1, y=2
foo(10);      // x=10, y=2
foo(10, 20);  // x=10, y=20

// ❌ 不合法:不能只传 y
foo( , 20);   // 错误!不能跳过 x

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值