0
点赞
收藏
分享

微信扫一扫

【C++初阶】第一站:C++入门基础(中)

前言:

目录

5. 函数重载 (续)

C++支持函数重载的原理--名字修饰(name Mangling)

为什么C++支持函数重载,而C语言不支持函数重载呢?

6. 引用

引用概念

关于引用的应用:

引用特性

🚩引用在定义时必须初始化

🚩一个变量可以有多个引用

🚩引用一旦引用一个实体,再不能引用其他实体

使用场景

传值、传引用效率比较

值和引用的作为返回值类型的性能比较

关于顺序表的读取与修改

c语言接口

Cpp的接口设计:

常引用


5. 函数重载 (续)

C++支持函数重载的原理--名字修饰(name Mangling)

编译器是如何编译的?

Test.cpp

Test.i

Test.s

Test.o

在整个编译的过程中涉及到的一个问题是什么呢?

 C语言的特点呢 -- 直接用函数名去充当函数的名字

        这样的后果就是自己都区分不开来,所以C语言是不允许重名的。

那么C++是如何这块的问题呢?如何把两个同名的但参数类型顺序不一样的函数区分开来呢?

🎯 那就是函数名修饰规则解决这个问题

当函数只有声明没有定义的时候,就会出现以下的链接错误

        🌼当函数只有定义的时候,没有实现的时候,它就没有一堆汇编指令,没有指令就不能生成地址(就没有建立函数栈帧的过程,寄存器没有存地址)。所以在符号表里面拿这个名字去找的时候就找不到,以下是修饰以后的函数名,本质上它是用类型带入这个名字里面去了(函数修饰规则)

而C语言是直接用函数名去找:

在linux环境底下去看:

以下两张图均是函数名修饰规则

        Linux底下:c++区分函数不同的依据是去找函数的地址(本质也就是第一句指令的地址),找到地址之后,将其函数名修饰成特殊函数名:

🍔在符合表里面:

        用一个独特的符号去代表一个类型,跟据类型的个数不同、类型不同、类型的顺序不同,修饰出了的名字就是不一样的,所以根据这点,就可以在函数名相同的情况下区分不同的函数。

vs2019底下:

问题:

函数名修饰规则带入返回值,返回值不能能否构成重载? 不能。

为什么C++支持函数重载,而C语言不支持函数重载呢?

在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接

6. 引用

引用概念

💤现实生活来说:比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"

        

        C++为了在拓展语法的过程中,为了防止创新符号太多不好记忆,直接沿用C语言的符号,让其一个符号赋予了多重意思,&在这边不是取地址的意思,而是引用操作符

当b++时,a也会同时++,当两条语句都++时,那么这个值就变为2

代码实现:

int main()
{
int a = 0;
int a;//引用

cout <<
cout <<

return 0;

}

注意: 引用类型必须和引用 实体同种类型

关于引用的应用:

1️⃣简单的应用,值的交换:

void swap(int& x1, int& x2)
{
int tmp = x1;
x1 = x2;
x2 = tmp;
}
int main()
{
int size;

int x = 0, y = 1;
swap(x,y);
printf("%d %d", x, y);
}

执行:

2️⃣二叉树前序遍历的应用

int TreeSize(struct TreeNode* root)
{
//写法一
if (root == NULL)
return 0;
//写法二
return TreeSize(root->left)
+ TreeSize(root->right)
+ 1;
}
void _preorder(struct TreeNode* root, int* a, int& pi)
{
if (root == NULL)
return;
//用指针的方式是为了不在不同栈帧内创建i
a[pi] = root->val;
pi++;
_preorder(root->left, a, pi);
_preorder(root->right, a, pi);
}
int* preorderTraversal(struct TreeNode* root, int& returnSize)
{
int size = TreeSize(root);
int* a = (int*)malloc(sizeof(int) * sizeof(int));

int i = 0;
_preorder(root,a,i);
return a;
}
int main()
{
int size = 0;
preorderTraversal(nullptr, size);

}

执行:

3️⃣关于单链表的链接

前后代码对比:

引用特性

🚩引用在定义时必须初始化

🚩一个变量可以有多个引用

int main()
{
int a = 0;
int a;
int a;
int b;//给别名取别名,实际上是同一块空间。

int x = 1;
//赋值
b = x;

return 0;
}

代码执行变化:

🚩引用一旦引用一个实体,再不能引用其他实体

int main()
{
int a = 0;
int a;

int x = 1;
int x;

return 0;
}

代码执行:

使用场景

传引用返回和传值返回:

以下的两者返回的方式有什么区别呢?

        答:这两种情况的区别,在于传值调用在函数销毁时有寄存器,传引用调用没有寄存器保存值,因为是同一个空间,引用不同于传值和传址,既然直接传的就是这个空间本身,因此既不用创建临时拷贝,也不需要传变量地址。而是直接变量进行赋值。

💥先来看传值返回:

        用了一个全局的寄存器eax把返回值保存起来,待Count函数栈帧销毁后,回到主函数main,再将寄存器里面的值赋值给ret

💨传引用返回

        这个n的别名,出了作用域就销毁(这意味着返回的值是对已被销毁的变量的引用),还给操作系统了,在还给操作系统的时候,可能将这块空间里面的值给清理了,变成随机值了。由于Count函数的返回值是无效的(引用已被销毁),所以打印ret的值将导致未定义的行为。它可能打印1,也可能打印随机值,或者可能导致程序崩溃。

💢那如果对代码再修改一下呢:

        这段代码是非法的。当函数 Count() 执行完毕后,局部变量 n 将被销毁,引用 ret 将会成为悬空引用(dangling reference),它指向了已经归还给操作系统的空间,该空间里面的值可能已经被初始化为随机值。因此,将对n的引用返回给调用函数是无效的,导致未定义的行为。

 总结:

代码示例:

//传引用调用
int& Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int Count();
//这里打印的结果可能是1,也可能是随机值
cout << ret << endl;
cout << ret << endl;

return 0;
}
//传值调用
int Count()
{
int n = 0;
n++;


return n;
}
int main()
{
int ret = Count();

cout << ret << endl;
cout << ret << endl;

return 0;
}

当变量前面有很大一块空间被占用时,有可能不会被覆盖:

        写一个相加两个变量值的代码:

代码实现:

#include<iostream>
#include<assert.h>
int& Add(int a,int b)
{
int c = a + b;
return c;
}

int main()
{
int Add(1, 2);
Add(3, 4);
cout << "Add(1,2) is :" << ret << endl;
}

解析:

传值、传引用效率比较

           以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效

率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

        

#include <time.h>
#include<iostream>

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;
}

 值和引用的作为返回值类型的性能比较

#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;
}

关于顺序表的读取与修改

c语言接口
struct SeqList
{
int a[10];
int size;
};

//C的接口设计
//读取第i个位置
int SLAT(struct SeqList* ps, int i)
{
assert(i<ps->size);//防止越界
//...
return ps->a[i];
}
//修改第i个位置的值
void SLModify(struct SeqList* ps, int i, int x)
{
assert(i< ps->size);
// ...
ps->a[i] = x;
}

        可以看待以上代码,C语言实现读取和修改结构体成员--数组元素时,是非常繁琐的,实现功能就要编写一个功能函数,但是如果换成c++的引用,那就可以一个函数实现两个功能

Cpp的接口设计:

代码示例:

CPP接口设计
//读 or 修改第i个位置的值
#include<iostream>
#include<assert.h>
int& SLAT(struct SeqList& ps, int i)
{
assert(i < ps.size);

return(ps.a[i]);

}
int main()
{
struct SeqList s;
s.size = 3;

SLAT(s, 0) = 10;
SLAT(s, 1) = 20;
SLAT(s, 2) = 30;
cout << SLAT(s, 0) << endl;
cout << SLAT(s, 1) << endl;
cout << SLAT(s, 2) << endl;

return 0;
}

特别注意:

 

常引用

int main()
{

const int a = 0;

//权限的放大
int a;//这个是不行的!!!

//权限的平移
const int a;

//权限的缩小
//形象地理解:
int x = 0;//齐天大圣
const int x;//戴上紧箍咒的孙悟空

return 0;
}

赋值:

int b = a;//可以的,因为这里是赋值拷贝,b修改不影响a

类型转换:

        在c/c++里面有个规定:表达式转换的时候会产生一个临时变量,具有常性

以及函数返回的时候也会产生一个临时对象

        本章未结束,尽快更新下一章完结此篇。

举报

相关推荐

0 条评论