【Cpp筑基】一、内联函数、引用变量、函数重载、函数模板

【Cpp筑基】一、内联函数、引用变量、函数重载、函数模板

1. 内联函数

C++提供了一种内联函数,在 C++ 中,内联函数(inline function)是一种特殊的函数,其定义使用 inline 关键字来提示编译器将函数调用直接替换为函数体,以减少函数调用的开销。内联函数通常用于简短、频繁调用的函数。

要使用内联函数,必须:

  • 在函数声明前加上关键字inline
  • 在函数定义前加上关键字inline

注意内联函数不能递归。内联函数的基本语法:

inline return_type function_name(parameters){
	// 函数体
}

举个例子:

#include <iostream>
using namespace std;

// 定义一个内联函数
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(3, 4);
    cout << "Result: " << result << endl;
    return 0;
}

输出结果

Result: 7

inline工具是C++新增的特性,C语言使用预处理器语句#define来提供宏——内联代码的原始实现。例如:

#define SQUARE(X) ((X)*(X))

inline#define的主要区别是:

  1. 实现机制:
    • 内联函数是由编译器在编译时展开的,是一种编译时的语言特性。编译器会在调用内联函数的地方直接替换函数体。
    • #define宏是在预处理阶段展开的,是一种文本替换的机制。预处理器会在编译前把宏展开。
  2. 类型检查:
    • 内联函数有类型检查,编译器会检查参数类型是否匹配。
    • #define宏是简单的文本替换,没有类型检查,容易出现类型错误。
  3. 时间和空间开销:
    • 内联函数在编译时展开,没有函数调用的开销,但会增加程序的大小,增加空间成本。
    • #define宏在预处理阶段展开,没有函数调用开销,但可能会导致代码膨胀。

2. 引用变量

C++新增了一种复合类型——引用变量,使用运算符&,引用变量的主要用途是作为函数参数列表中的形参。例如,创建一个引用变量

int rats;
int & rodents = rats;

注意:引用必须在声明时进行初始化,而不能像指针一样,先声明再赋值。引用一旦与变量进行关联,就一直指向这个变量。

C++11中新增了另一种引用——右值引用(rvalue reference),这种引用可以指向右值,使用&&进行声明。

在 C++ 中,“左值”(lvalue)和“右值”(rvalue)是用来描述表达式值类型的一对术语。理解它们的概念对于掌握 C++ 语言的赋值、引用、移动语义等方面的内容非常重要。

左值(lvalue,locatable value)是指能够定位的值,它表示存储在内存中的某个位置的对象。因此,左值是可以取地址的,可以出现在赋值操作的左侧。例如:

int x = 10;   // x 是左值
int *p = &x;  // 可以取地址
x = 20;       // 可以出现在赋值操作的左侧

右值(rvalue,readable value)是指不具有持久存储位置的临时值,它通常是表达式求值的结果。右值不能取地址,也不能出现在赋值操作的左侧。例如:

int y = 10;     // 10 是右值
int z = y + 5;  // (y + 5) 是右值
int *p = &10;   // 错误,不能对右值取地址

左值引用是对左值的引用,用于绑定左值。例如:

int a = 10;
int &ref = a;  // 左值引用
ref = 20;      // 可以通过引用修改 a 的值

右值引用是对右值的引用,用于绑定右值。这是 C++11 引入的特性,主要用于实现移动语义和完美转发,以提高性能。

int &&rref = 10;  // 右值引用
rref = 20;        // 可以通过引用修改右值

新增右值引用主要是用于移动语义完美转发,理解左值和右值的区别是掌握 C++ 高级特性(如移动语义和完美转发)的基础。

引用常用于函数的参数传递,这样可以避免不必要的拷贝,并且允许函数修改传入的参数值。例如:

void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

int main() {
    int x = 10, y = 20;
    swap(x, y); // x 和 y 的值被交换
}

注意,在使用引用进行函数的参数传递的时候,我们应该尽可能使用const,将引用参数声明为常量数据的引用的理由有三个:

  • 使用const可以避免无意中修改数据的编程错误
  • 使用const使函数能够处理const和非const实参,否则将只能接受非const数据
  • 使用const引用使函数能够正确生成并使用临时变量

举个例子:

#include <iostream>
#include <string>

// 不使用 const 引用
void printString(std::string& s) {
    std::cout << "Non-const reference: " << s << std::endl;
    s = "modified";
}

// 使用 const 引用
void printConstString(const std::string& s) {
    std::cout << "Const reference: " << s << std::endl;
    // s = "modified"; // 错误,无法修改 const 引用
}

int main() {
    std::string str = "hello";

    // 使用非 const 引用
    printString(str);
    std::cout << "After printString(): " << str << std::endl;

    // 使用 const 引用
    printConstString(str);
    std::cout << "After printConstString(): " << str << std::endl;

    return 0;
}

输出如下:

Non-const reference: hello
After printString(): modified
Const reference: modified
After printConstString(): modified

什么时候使用引用和指针呢?

使用引用参数的主要原因有两个:

  • 程序员能够修改调用函数中的数据对象
  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度。

对于使用传递的值而不做修改的函数:

  • 如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为const

3. 函数重载

C++实现多态有两种方式,

  • 编译时多态Compile-time Polymorphism(通过函数重载和模板实现)
  • 运行时多态Runtime Polymorphism(通过继承和虚函数实现)

函数重载的关键是函数的参数列表(也称为特征标),如果两个函数的参数数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是不重要的。C++允许定义相同名称的函数前提是他们的特征标不同。

举个例子,编译时多态可以通过函数重载或者是通过模板进行实现:

  1. 通过函数重载进行实现
// 使用函数重载实现多态
#include <iostream>

void print(int i) {
    std::cout << "Integer: " << i << std::endl;
}

void print(double d) {
    std::cout << "Double: " << d << std::endl;
}

void print(const std::string& str) {
    std::cout << "String: " << str << std::endl;
}

int main() {
    print(42);         // 调用 void print(int)
    print(3.14);       // 调用 void print(double)
    print("Hello");    // 调用 void print(const std::string&)
    return 0;
}
  1. 通过函数模板进行实现
#include <iostream>

template<typename T>
void print(T value) {
    std::cout << "Value: " << value << std::endl;
}

int main() {
    print(42);          // T 被推断为 int
    print(3.14);        // T 被推断为 double
    print("Hello");     // T 被推断为 const char*
    return 0;
}

C++中的运行时多态(Runtime Polymorphism)是通过继承和虚函数(virtual functions)实现的。这种多态性允许在运行时根据对象的实际类型调用适当的方法,而不是在编译时决定调用哪个函数。运行时多态的核心是使用基类指针或引用来操作派生类对象。

运行时多态主要依赖以下的几个概念:

  • 继承(Inheritance):允许一个类继承另一个类的属性和方法。
  • 虚函数(Virtual Functions):在基类中声明为virtual的函数,可以在派生类中被重写。
  • 虚函数表(Virtual Table, vtable):编译器为包含虚函数的类生成的一个表,表中存储了类的虚函数指针。每个对象包含一个指向其类的虚函数表的指针。
#include <iostream>

// 基类
class Shape {
public:
    // 纯虚函数,定义接口
    virtual void draw() const = 0; // = 0 表示纯虚函数,必须在派生类中实现
    virtual ~Shape() {}            // 虚析构函数,以确保派生类的析构函数被调用
};

// 派生类:Circle
class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing Circle" << std::endl;
    }
};

// 派生类:Rectangle
class Rectangle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

int main() {
    // 创建基类指针数组,指向派生类对象
    Shape* shapes[2];
    shapes[0] = new Circle();
    shapes[1] = new Rectangle();

    // 遍历数组并调用虚函数
    for (int i = 0; i < 2; ++i) {
        shapes[i]->draw(); // 根据对象的实际类型调用相应的draw方法
    }

    // 释放内存
    for (int i = 0; i < 2; ++i) {
        delete shapes[i];
    }

    return 0;
}

4. 函数模板

函数模板有两种定义方式,第一种使用关键字templatetypename,例如

template<typename T>
void func (T & a);			// 随便定义一个函数

第二种是使用关键字templateclass,例如

template<class T>
void func (T & a);			// 随便定义一个函数

其中,template<typename T> 是模板头,表示这个函数是一个模板函数,T是一个类型参数,可以是任意合法的类型。T可以用在函数的返回类型、参数列表和函数体内。

  1. 模板类型可以有多种形式,不仅限于一个,例如:
template<typename T1, typename T2>
void print(T1 a, T2 b) {
    std::cout << a << " " << b << std::endl;
}

int main() {
    print(10, " apples");     // T1 被推断为 int,T2 被推断为 const char*
    print(3.14, 42);          // T1 被推断为 double,T2 被推断为 int
    return 0;
}
  1. 除了类型参数,模板还可以具有非类型参数,例如:
template<typename T, int N>
T getArrayElement(T (&arr)[N], int index) {
    return arr[index];
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    std::cout << getArrayElement(arr, 2) << std::endl;  // 输出 3
    return 0;
}

在这个例子中,N是一个非类型模板参数,它表示数组的大小。

  1. 模板参数也可以有默认值,例如
template<typename T = int>
T multiply(T a, T b) {
    return a * b;
}

int main() {
    std::cout << multiply(3, 4) << std::endl;    // 使用默认类型 int
    std::cout << multiply(3.14, 2.0) << std::endl; // 显式指定类型为 double
    return 0;
}
  1. 模板还允许提供具体化版本,即对特定的类型提供不同的版本,例如:
#include <iostream>
#include <string>

// 模板函数定义
template<typename T>
T add(T a, T b) {
    return a + b;
}

// 对 std::string 的完全特化
template<>
std::string add<std::string>(std::string a, std::string b) {
    return a + " specialized " + b;
}

int main() {
    std::cout << add(3, 4) << std::endl;          // 输出 7
    std::cout << add(3.5, 2.1) << std::endl;      // 输出 5.6
    std::cout << add<std::string>("Hello, ", "World!") << std::endl;  // 输出 Hello,  specialized World!
    return 0;
}

函数模板具有显式具体化机制,显式具体化就是为特定的类型提供函数模板的特化版本,这里针对std::string类型提供了一个不同版本的add函数。

Reference

《C++ Prime Plus》

  • 15
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

木心

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值