C语言向C++过渡的基础知识(二)

目录

C++中的函数重载

函数重载介绍

函数类型的使用

C++中的引用

引用的介绍

引用的使用

引用与指针的对比

常量引用

引用在实际中的使用

函数返回值

返回值和返回引用时间消耗对比

函数形式参数

形式参数为值与形式参数为引用时间消耗对比

内联函数

内联函数的特点


C++中的函数重载

函数重载介绍

在C语言中,不能出现名字相同的函数,因为C语言中的编译过程函数是通过函数名以及对应的地址来找到函数并调用的,如果名字相同,那么编译器将无法分辨是哪一个函数

在C++中,允许在同一作用域下使用相同的函数名配合参数不同、参数类型和不同参数类型的不同顺序构成函数重载

//参数类型不同的重载
//整型参数
int add(int a, int b)
{
    return a + b;
}
//浮点型参数
double add(double a, double b)
{
    return a + b;
}

//参数个数不同的重载
//两个参数
int add(int a, int b)
{
    return a + b;
}
//三个参数
int add(int a, int b, int c)
{
    return a + b + c;
}

//不同类型的但数量相同情况下的不同参数顺序的重载
//注意变量名不需要更改顺序
//int类型在前,char类型在后
int getcharacter(int a, char b)
{
    return b;
}
//char类型在前,int类型在后
int getcharacter(char a, int b)
{
    return a;
}

📌

注意返回类型不可以作为判断函数是否重载的条件,函数重载只有三种特点:

  1. 参数个数不同
  2. 参数类型不同
  3. 相同个数相互类型不同情况下,不同的参数顺序

函数重载一般用于针对不同类型实现相同的功能,例如

//函数重载
//针对整型的加法函数
int add(int a, int b)
{
    return a + b;
}

//针对浮点型的加法函数
double add(double a, double b)
{
    return a + b;
}

函数类型的使用

在没有进入类之前,对于全局域的函数重载来说,正常调用对应函数名,但是需要传递正确的实参,例如

//整型加法
int add(int a, int b)
{
    return a + b;
}
//浮点型加法
double add(double a, double b)
{
    return a + b;
}
#include <iostream>
using namespace std;

int main()
{    
    //传哪种类型值就调用哪种类型的函数
    cout <<"整型加法:" << add(1, 2) << endl;
    cout <<"浮点型加法:"<< add(1.1, 2.2) << endl;
    return 0;
}
输出结果:
整型加法:3
浮点型加法:3.3

如果使用命名空间,则需要满足需要重载的函数在同一个命名空间,并且使用域作用限定符调用函数,传递实参时依旧是满足传哪种类型的值就调用哪种类型的函数

注意,含有缺省参数的函数和无缺省参数的函数无法构成函数重载,例如

//缺省参数与函数重载
int add(int a = 10, int b = 20)
{
    return a + b;
}

int add(int a, int b = 30)
{
    return a + b;
}
//将会报错为add函数重定义

📢

了解:

C++是通过函数名修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载

例如在Linux平台下用g++编译的函数重载后的函数名为_Z3addii(对应两个int类型参数的add函数),_Z3adddd(对应两个double类型参数的add函数),两个函数名中的3为函数本身(不包括类型)的名字长度,例如add函数名字是3个字符的长度

C++中的引用

引用的介绍

在C语言中,为了可以通过其他变量直接改变某一个变量的值由此引出了指针的概念,通过指针类型可以改变该指针指向的空间的内容

在C++中,使用引用来辅助指针的使用,例如:

📌

注意引用的底层依旧是指针实现

//指针中的swap函数
void swap(int* num1, int* num2)
{
    int tmp = *num1;
    *num1 = *num2;
    *num2 = tmp;
}

//使用引用的函数
void swap(int& num1, int& num2)
{
    int tmp = num1;
    num1 = num2;
    num2 = tmp;
}

#include <iostream>
using namespace std;

int main()
{

    //C语言中的swap函数
    int a = 1;
    int b = 0;
    cout << "指针交换" << endl;
    cout << "交换前:" << endl;
    cout << a << " " << b << endl;
    swap(&a, &b);
    cout << "交换后:" << endl;
    cout << a << " " << b << endl;

    //C++中使用引用的swap函数
    a = 1;
    b = 0;
    cout << endl;
    cout << "引用交换" << endl;
    cout << "交换前:" << endl;
    cout << a << " " << b << endl;
    swap(a, b);
    cout << "交换后:" << endl;
    cout << a << " " << b << endl;

    return 0;
}
输出结果:
指针交换
交换前:
1 0
交换后:
0 1

引用交换
交换前:
1 0
交换后:
0 1

在C++中,引用相当于为变量取一个别名(引用变量),这个别名和其对应的变量(引用实体)共用一个地址,所以可以通过引用直接改变对应的变量所在空间的内容

所以在上面的代码中,对于引用变量的函数void swap(int& num1, int& num2)num1num2为实参ab的别名,所以此时num1num2ab的地址相同,直接在swap函数改变num1num2的值即可改变实参ab中的值

引用的使用

在C++中,使用下面的语法创建引用变量

引用实体的类型 &引用变量名 = 引用实体;

📌

此处的引用实体可以是任何类型,包括指针类型以及引用类型

//引用与指针
#include <iostream>
using namespace std;

int main()
{
    int a = 0;
    //指针类型
    int* p = &a;
    cout << "指针修改前:" << endl;
    cout << a << endl;
    *p = 1;
    cout << "修改后:" << endl;
    cout << a << endl;

    //引用类型
    a = 0;
    int& rp = a;
    cout << "引用修改前:" << endl;
    cout << a << endl;
    rp = 1;
    cout << "修改后:" << endl;
    cout << a << endl;

    //引用实体为指针类型
    a = 0;
    int*& rpp = p;
    cout << "引用类型为指针修改前:" << endl;
    cout << a << endl;
    *rpp = 1;
    cout << "修改后:" << endl;
    cout << a << endl;

    //引用变量作为引用实体
    a = 0;
    int& r = rp;
    cout << "引用类型为引用变量修改前:" << endl;
    cout << a << endl;
    rp = 1;
    cout << "修改后:" << endl;
    cout << a << endl;
    return 0;
}
输出结果:
指针修改前:
0
修改后:
1
引用修改前:
0
修改后:
1
引用类型为指针修改前:
0
修改后:
1
引用类型为引用变量修改前:
0
修改后:
1

引用与指针的对比

在C++中,引用有以下的特点:

  1. 引用在定义时必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,就不能再引用其他实体(即不可以更改实体)
  4. 没有NULL引用
  5. 使用sizeof关键字计算某个引用的大小时,计算的是引用实体的类型大小,而不是一个固定值
  6. 引用变量自加1相当于对应实体内容加1
  7. 引用没有多级引用,即没有二级及以上引用
  8. 引用在访问指向的实体时不需要解引用,由编译器处理,除非实体是指针类型
  9. 引用在概念上没有实际地址

对比指针的特点:

  1. 指针在使用之前可以不初始化,但是在VS上面必须初始化
  2. 一个变量可以有多个指针
  3. 一个指针指向一个变量时,可以改变其指向,使其指向下一个变量
  4. NULL指针
  5. 使用sizeof关键字计算某个指针的大小时,计算的是指针变量的大小,是一个固定值,在32位机上值为4,在64位机上是8
  6. 指针变量自加1相当于跳过指向的类型占用的内存大小个字节
  7. 指针存在二级及以上的引用
  8. 指针不论指向的类型如何,都必须显式解引用
  9. 指针在概念上和实际上都有自己的实际地址
//指针可以更改指向,引用不可以
#include <iostream>
using namespace std;

int main()
{
    int a = 0;
    //两个引用指向同一个变量a
    //变量a给引用变量b
    int& b = a;
    cout << "初始a中的值:" << a << endl;
    //通过引用b改变a的值
    b = 20;
    cout << "通过引用改变a中的值:" << a << endl;

    //改变引用a实体的引用变量b使其指向c
    cout << "初始a中的值:" << a << endl;
    int c = 10;
    cout << "初始c中的值" << c << endl;
    b = c;
    cout << "现在a中的值:" << a << endl;
    //通过引用b改变c的值
    b = 30;
    cout << "当前c中的值" << c << endl;
    cout << "当前a中的值:" << a << endl;

    return 0;
}
输出结果:
初始a中的值:0
通过引用改变a中的值:20
初始a中的值:20
初始c中的值10
现在a中的值:10
当前c中的值10
当前a中的值:30
//从上面的结果中可以看到,尽管b引用显式得改变了指向,使其指向c,但是实际上只是将c中的值给了b引用,而因为引用b的指向不可以改变,故依旧改变的是a中的值

//sizeof
#include <iostream>
using namespace std;

int main()
{
    //一个变量可以有多个引用和指针
    int a = 0;
    //两个引用指向同一个变量a
    int& r = a;
    //两个指针指向同一个变量a
    int* p = &a;
    int*& r2 = p;

    cout << "计算引用变量大小:(引用int类型)" << sizeof(r) << endl;
    cout << "计算引用变量大小:(引用int*类型)" << sizeof(r2) << endl;
    cout << "计算引用指针大小:(64位机,指向int类型)" << sizeof(p) << endl;

    return 0;
}
输出结果:
计算引用变量大小:(引用int类型)4
计算引用变量大小:(引用int*类型)8
计算引用指针大小:(64位机,指向int类型)8

//引用变量和指针+1操作
#include <iostream>
using namespace std;

int main()
{
    int arr[4] = { 1,2,3,4 };
    int& a1= arr[0];
    int* a2 = &arr[0];
    //const int*& a4 = &arr[0];//无法初始化
    cout << "计算引用实体类型为int类型+1跳过的大小" << endl;
    cout << "开始位置:" << &arr[0] << " " << "对应数值:" << arr[0] << endl;
    cout << "结束位置对应数值:" << a1 + 1 << endl;
    cout << "计算指针+1跳过的大小" << endl;
    cout << "开始位置:" << &arr[0] << " " << "对应数值:" << arr[0] << endl;
    cout << "结束位置:" << a2 + 1 << endl;
    cout << "计算引用实体类型为int*类型+1跳过的大小" << endl;
    a2 = arr;
    int*& a3 = a2;
    const int*& a4 = &arr[0];//注意不可以使用这种写法,因为当前编译器不知道到底右侧值是否可以修改
    cout << "开始位置:" << &arr[0] << " " << "对应数值:" << arr[0] << endl;
    cout << "结束位置:" << a3 + 1 << endl;

    return 0;
}
输出结果:
计算引用实体类型为int类型+1跳过的大小
开始位置:000000970C4FF7B8 对应数值:1
结束位置对应数值:2
计算指针+1跳过的大小
开始位置:000000970C4FF7B8 对应数值:1
结束位置:000000970C4FF7BC
计算引用实体类型为int*类型+1跳过的大小
开始位置:000000970C4FF7B8 对应数值:1
结束位置:000000970C4FF7BC

//引用本身的地址和指针本身的地址
#include <iostream>
using namespace std;

int main()
{
    //一个变量可以有多个引用和指针
    int a = 0;
    //两个引用指向同一个变量a
    int& r = a;
    //两个指针指向同一个变量a
    int* p = &a;
    int*& r2 = p;

    cout << "引用实体为int类型变量的地址:" << &r << endl;
    cout << "引用实体为int*类型变量的地址" << &r2 << endl;
    cout << "指针变量指向的int类型变量的地址" << p << endl;
    cout << "指针变量自身的地址" << &p << endl;
    return 0;
}
输出结果:
引用实体为int类型变量的地址:0000000C97EFF5A4
引用实体为int*类型变量的地址0000000C97EFF608
指针变量指向的int类型变量的地址0000000C97EFF5A4
指针变量自身的地址0000000C97EFF608

常量引用

在C语言中,可以使用const指针修饰指针变量,并且const可以修饰指针变量整体使得无法通过解引用该指针修改指向空间的内容,也可以放在变量名前方限制指针无法指向其他对象

在C++中,也存在const修饰的引用,但是const只能放在引用变量整体的前面,即const int& a,例如

💡

在指针/引用中,指针和引用的权限只可以缩小不可以放大

//常量引用
#include <iostream>
using namespace std;

int main()
{
    //对于常量来说,需要使用常量引用变量才能引用常量实体
    const int a = 0;
    const int& a1 = a;
    const int& a2 = 10;//10为常量值
    double d = 2.34;
    //int& a3 = d;//无法编译,但不是类型不同的原因
    const int& a3 = d;//可以编译
    //const int* p = d;//但是指针无法做到同样的实现,原因是类型不同
    return 0;
}

💡

在强制类型转换的过程中,实际上是创建了一个临时变量,例如

double b = 2.34;

int a = (int)b;

在此处的强制转换过程中,a被赋值为强制转换为int类型的b,但是b本身的值并没有改变,b中存储的值依旧是2.34,但是a中存的值为2,所以强制转换过程中不会直接对需要强制转换的对象b进行改变,而是取出该变量b中的数据放到临时变量中并转换为对应类型再给目标对象a

此时可以解释为什么上面的引用需要加const才可以编译通过

在上面的代码中ddouble类型的变量,而需要强制转换为int类型的变量给int类型的引用,但是在强制转换过程中,是先创建了临时变量存储的d中的值,而临时变量中的值为常量值不可以被改变,所以需要给int类型的引用加const才可以成功编译

引用在实际中的使用

函数返回值

//返回值
int add(int a, int b)
{
    static int z = a + b;
    return z;
}

#include <iostream>
using namespace std;

int main()
{
    int ret = add(1, 2);
    cout << ret << endl;
    return 0;
}
输出结果:
3

//返回引用
int& add(int a, int b)
{
    static int z = a + b;
    return z;
}

#include <iostream>
using namespace std;

int main()
{
    int ret = add(1, 2);
    cout << ret << endl;
    return 0;
}
输出结果:
3

在上面的代码中,add函数中有一个static类型的变量,直接返回值时,需要将该变量的值存入寄存器中,再由寄存器带回给add函数被ret接收,而使用引用返回该类型的变量可以在一定程度上减少前面的步骤消耗

但是,注意下面的代码可能会出现问题

#include <iostream>
using namespace std;

int& add(int a, int b)
{
    int z = a + b;
    return z;
}

int main()
{
    int& ret = add(1, 2);
    //int ret = add(1, 2);//依旧会有问题
    cout << ret << endl;
    cout << "输出结果:" << ret << endl;
    return 0;
}
输出结果:
3
输出结果:-858993460
//编译器警告:返回局部变量或临时变量的地址: z

因为zadd函数中的局部变量,出了add函数将会被销毁,此时返回z是返回变量z的引用,被ret接收后,ret指向z的位置,当z销毁后,ret依旧指向该位置,但是该位置的值已经不一定是刚才计算出来的结果,因为add函数栈帧空间可能会被覆盖,没被覆盖时该地址的值依旧是计算出来的值,否则就是随机值,具体取决于编译器如何分配空间

📌

在使用引用作为函数返回值时,注意引用的实体尽量不是临时变量或者局部变量,除非返回作为形式参数的引用变量

所以,如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回

返回值和返回引用时间消耗对比
#include <iostream>
using namespace std;

#include <time.h>
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
    // 以值作为函数的返回值类型
    size_t begin1 = clock();
    for (size_t i = 0; i < 100000; ++i)
        TestFunc1();
    size_t end1 = clock();
    // 以引用作为函数的返回值类型
    size_t begin2 = clock();
    for (size_t i = 0; i < 100000; ++i)
        TestFunc2();
    size_t end2 = clock();
    // 计算两个函数运算完成之后的时间
    cout << "TestFunc1 time:" << end1 - begin1 << endl;
    cout << "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
    TestReturnByRefOrValue();
    return 0;
}
输出结果:
TestFunc1 time:161
TestFunc2 time:1

函数形式参数

#include <iostream>
using namespace std;

void swap(int& num1, int& num2)
{
    int tmp = num1;
    num1 = num2;
    num2 = tmp;
}

int main()
{
    int a = 1;
    int b = 0;
    cout << "交换前的a和b:" << "a = " << a << " " << "b = " << b << endl;
    swap(a, b);
    cout << "交换后的a和b:" << "a = " << a << " " << "b = " << b << endl;
    return 0;
}
输出结果:
交换前的a和b:a = 1 b = 0
交换后的a和b:a = 0 b = 1
形式参数为值与形式参数为引用时间消耗对比
#include <iostream>
using namespace std;
#include <time.h>
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
    A a;
    // 以值作为函数参数
    size_t begin1 = clock();
    for (size_t i = 0; i < 10000; ++i)
        TestFunc1(a);
    size_t end1 = clock();
    // 以引用作为函数参数
    size_t begin2 = clock();
    for (size_t i = 0; i < 10000; ++i)
        TestFunc2(a);
    size_t end2 = clock();
    // 分别计算两个函数运行结束后的时间
    cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
    cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
    TestRefAndValue();
    return 0;
}
输出结果:
TestFunc1(A)-time:15
TestFunc2(A&)-time:0

内联函数

在C语言中,常量和宏函数一般用#define来定义,优点是:增强代码的复用性以及提高性能,但是#define定义的二者在预处理时将会替换其在函数出现的位置,从而导致下面的三个重要的问题

  1. #define定义的宏函数和常量无法被调试
  2. 代码可读性差,可维护性差,并且容易误用
  3. #define定义的宏函数没有类型的检查

在C++中,为了避免上述问题,经常使用constenum来表示常量,而使用内联函数代替#define定义的宏函数

所谓内联函数,即当函数代码量较小时,在汇编代码中直接将函数代码替换掉原来的call+函数名指令,减少函数栈帧开辟时的开销,从而实现直接执行函数,但是缺点也就比较明显,如果代码量太大,将会导致替换后的函数指令很多,为此C++中的内联函数到底是真正的函数还是类似于C语言的宏函数取决于函数代码规模的大小,如果函数代码量大,那么尽管定义为内联函数,还是会被编译器认作普通函数

在C++中,使用inline关键字修饰定义的函数,语法如下

inline + 普通函数定义

代码实例

//C语言中#define定义的ADD宏函数
#define ADD(x, y) ((x) + (y))

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

📌

不建议将内联函数的声明和定义分开写,二者分离会导致链接错误。因为inline会展开函数,导致函数失去本身的地址,声明的函数就无法被找到

//函数声明
inline int add(int a, int b);

//函数定义
int add(int a, int b)
{
    return a + b;
}
//编译报错为链接错误

内联函数的特点

inline是一种以存储空间(不是内存空间)换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大(因为函数展开之后汇编代码量增多),优势:少了调用开销,提高程序运行效率

相关推荐

  1. C语言C++过渡基础知识

    2024-03-18 18:38:02       19 阅读
  2. C语言C++过渡基础知识(一)

    2024-03-18 18:38:02       22 阅读
  3. c语言基础知识

    2024-03-18 18:38:02       37 阅读
  4. C语言基础知识笔记

    2024-03-18 18:38:02       18 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-18 18:38:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-18 18:38:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-18 18:38:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-18 18:38:02       18 阅读

热门阅读

  1. 阐述Dubbo的并发控制原理

    2024-03-18 18:38:02       16 阅读
  2. 程序员应该如何选择职业赛道?

    2024-03-18 18:38:02       17 阅读
  3. 鸿蒙内核系统

    2024-03-18 18:38:02       20 阅读
  4. 5.66 BCC工具之offwaketime.py解读

    2024-03-18 18:38:02       15 阅读