0
点赞
收藏
分享

微信扫一扫

C和C++的区别(引用&)


文章目录

  • ​​一、关于inline函数​​
  • ​​二、函数重载​​
  • ​​三、C和C++函数相互调用​​
  • ​​四、C和C++中的const​​
  • ​​五、引用和指针的区别​​
  • ​​六、左值和右值​​

一、关于inline函数

当函数的调用开销远远大于函数本身起作用的指令时,需要使用内联函数,从而省去函数调用开销。

内联函数:在 编译期 的时候,内联函数的代码会在 调用的地方展开没有函数栈帧的开辟

inline函数

  1. 编译期间在代码调用的地方展开,有逻辑性的进行文本替换,因此不产生函数符号
  2. 能够调试,在debug版本(需要调试)inline函数和普通的函数表现一致,只有在release版本才会真正在调用点展开
  3. 因为需要在编译期间展开,而编译期间针对的是单文件。所以inline函数的作用域只在本文件,debug版本生成local符号
  4. 有类型检测,安全
  5. 类体内实现的成员方法直接是inline

宏函数
6. 预编译期展开在代码调用点,进行简单的文字替换,
7. 不能调试
8. 没有类型检测,不安全

static函数

  1. 不展开
  2. 能调试
  3. 作用域在本文件,生成local符号
  4. 有类型检测,安全

普通函数

  1. 不展开
  2. 能调试
  3. 作用域在全局,生成global符号
  4. 有类型检测,安全

递归函数 :不可能被处理成内联,内联要在编译期展开,而在编译期间无法获得递归的终止条件(中止条件由变量构成,而非常量,故编译期间无法获得中止条件)。inline 关键字只是对与编译器的建议,建议处理成内联,如果在递归函数前加上inline,也不会被处理成内联函数。

不是递归函数也未必被处理成内联,有的函数在调用处展开可能有语法错误。

//debug 版本生成local符号
inline int fun1(int a, int b)
{
int c = 10;
c = a + b;
return c;
}

//生成local符号
static int data = 0;
static int fun2()
{
return 0;
}


//生成global 符号
int data2;
int fun3()
{
return 0;
}

二、函数重载

C++函数能重载,C语言中不能的原因:C语言中生成函数符号依赖​​函数名​​​,C++中生成函数符号依赖 ​​ 函数名 + 参数列表​

什么样的函数能存在重载关系?

  1. 函数名相同,参数个数或类型不同,和返回值无关
  2. 重载函数必须处于同一作用域

函数重载是在编译期间确定的(生成符号),生成的符号是global的。

多态

静多态:编译期间的多态 (函数重载…)
动多态:运行期间的多态

bool compare(int a, int b)
{
cout << "bool compare(int a, int b)" << endl;
return a > b;
}

bool compare(float a, float b)
{
cout<< " bool compare(float a, float b)" <<endl;
return a > b;
}

bool compare(short a, short b)
{
cout << "bool compare(short a, short b) " << endl;
return a > b;
}

bool compare(const char* a,const char* b)
{
cout << "bool compare(char* a, char* b) " << endl;
return strcmp(a,b)>0;
}

int main(){
short c1 = 10;
short c2 = 20;

const char* str1 = "aaaa";
const char* str2 = "bbbb";

compare(a, b);
compare(c1, c2);
compare(str1, str2);
//compare(10.2, 20.3);
return 0;
}

​const​​​和​​volatile​​如何影响形参类型?

三、C和C++函数相互调用

在C++中引入C函数

main.cpp

// 使用C语言的方式生成函数符号
extern "C" {
// 函数符号function_C,而不是function_C_int
int function_C();
}


int main() {
function_C();
}

fun.cpp

#include<iostream>
using namespace std;

//function_CPP_int
int function_C(int a)
{
cout << "int function_CPP()" << endl;
return 0;
}

fun.c

#include<stdio.h>

//function_C
int function_C()
{
printf("int function_C()");
return 0;
}

在C中引入C++函数
main.c

int function_C();
int main() {
function_C();
}

fun.cpp

#include<iostream>
using namespace std;

extern "C"{
int function_C(int a){
cout << "int function_CPP()" << endl;
return 0;
}
}

在项目中,我们常常这样写提高代码的通用性

#ifdef __cplusplus
extern "C"{
#endif
int function_C(){
printf("int function_C()");
return 0;
}
#ifdef __cplusplus
}
#endif

在C++编译器中,都有​​__cplusplus​​​这个宏,若用C++编译器编译,则​​extern "C"​​​会起作用,按照C方式生成函数符号。若用C编译器编译,则​​extern "C"​​不会起作用,依然按照C方式生成函数符号。

四、C和C++中的const

1. 关于常量初始化

在C里const常量可以不初始化,而C++中必须初始化。若C语言中不初始化,只可采用指针的方式修改

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

2. 关于常量修改

#include<stdio.h>

int main() {
const int a = 10;
int c;
c = a;
c = 20;
return 0;
}

C和C++的区别(引用&)_r语言


通过指针修改C语言中的const量

C和C++的区别(引用&)_右值_02


C语言中的const变量只是一个不能作为左值的变量,除此以外和普通变量没有区别,通过指针可以修改。其实C++中的const也可以通过指针修改,但是需要强制类型转换

const int a = 10;
int* p = (int*)&a;
*p = 100;
printf("%d, %d, %d", a, *p, *(&a));

以上代码在C中,执行结果为:100,100,100

C++中的执行结果为:10,100,10

C和C++的区别(引用&)_右值_03


C和C++的区别(引用&)_c++_04


通过查看内存我们可以发现,a的内存空间已经通过指针修改,C++编译结果是直接push立即数给了printf,也就是在编译阶段就确定了打印的结果,和程序运行后修改内存没有关系。

正是因为编译阶段要进行替换,所以C++要求const变量必须初始化

若用立即数初始化,则编译阶段可以直接用立即数进行替换。若用变量初始化,则退化为和C语言中一模一样的常变量(只是不能作为左值,编译方式同普通变量)

#include<iostream>

using namespace std;

int main() {
const int a = 10;
int c;
c = a;
c = 100;
return 0;
}

C和C++的区别(引用&)_c语言_05

int main() {
int a = 1;
int b = 3;
const int res = a - b;
int c = res;
return 0;
}

C和C++的区别(引用&)_右值_06


编译期间无法确定const量res的值,故赋值的时候也是寻址(VS经过优化了,栈上的数据是不会生成符号的,一般通过栈底指针ebp偏移访问),而不是使用立即数

总结:C++中,若const只读量可以在编译期间替换,就会被替换成立即数。若不能替换,就是个不允许修改的只读量

3. const和指针的结合

const和一级指针结合:const修饰的是距离最近的类型

const int* p // 修饰*p
int const* p // 修饰*p
int* const p // 修饰p
const int* const p // 修饰*p和p

​const​修饰的类型:距离最近的类型
修饰的内容:类型除外就是内容(不可被改变

当const右边没有指针​​*​​时,const不参与组成类型

const int* p1;

以上​​const​​​修饰的类型是​​int​​​,表示指针指向的必须是​​int​​​类型。​​const​​​修饰的内容是​​*p1​​​,表示​​*p1​​​不允许改变,也即指针​​p1​​​指向的这个值不允许被改变,但是指针​​p1​​的指向可以改变。即:

*p1 = 100; // 不可修改指针指向的内容
p1 = &a; // 可以修改指针的指向

注意,指针​​p1​​​可以指向变量地址,只是不能通过指针​​p1​​修改所指内存空间的值

int a = 10;
int* const p2 = &a;//修饰类型:int*,修饰内容:p2,指向(p2)不可变,指向的内容(*p2)可以修改
*p2 = 100; // 允许

int b;
p2 = &b; // 不允许

int main() {
int a = 0;
int* p1;
const int* p2; // p2和p3其实一样
int const* p3;
int* const p4 = &a;

int* q1;
const int* q2; // 修饰类型:int,修饰内容:*q2,指向(q2)可以变,指向的内容(*q2)不允许修改
int const* q3; // 修饰类型:int,修饰内容:*q3
int* const q4 = &a; // 修饰类型:int*,修饰内容:q4,指向不可变,但是q4的指向的内容可以修改

p1 = q1;
p1 = q2; // error,q2指向一个常量,这样会把常量的地址泄露给非常量的指针
p1 = q3; // error,同上
p1 = q4; // p1和q4指向同一内存空间

p2 = q1;
p2 = q2;
p2 = q3; // 把q3指向的常量的地址给p2,但是p2也是常量指针,允许
p2 = q4;

p3 = q1;
p3 = q2;
p3 = q3;
p3 = q4;

// 以下试图修改p4的指向,全部不允许
p4 = q1;
p4 = q2;
p4 = q3;
p4 = q4;

return 0;
}

int* <= const int* 类型转换不允许
const int* <= int*

总结: 不允许泄露常量的地址给非常量的指针

const和二级指针结合

对于二级指针,两边必须相同
int** <= const int** 此处const修饰二级指针,错误
const int** <= int** 此处const修饰二级指针, 错误

int** <= int* const* 此处const修饰一级指针,错误
int* const* <= int** 此处const修饰一级指针,正确

关于二级指针的练习

练习1

这里​​*q​​​和​​p​​​都表示同一块内存,​​*q​​​里存放的可以是常量的地址(也就是不允许修改​​**p​​​),然而​​p​​​又是一个普通的指针,可以修改​​*p​​,这就导致常量地址泄露给了一个普通指针,所以错误。

int a = 10;
int* p = &a;
const int** q = &p;
/*
修改方案1:第二句改为const int* p = &a
修改方案2:第三句改为const int* const *q = &p
*/

对于​​const int* *q​​​而言,第二个​​*​​​表示q是指针变量,第一个​​*​​​和​​const int​​表示一起组成指针变量的类型

练习2

int a = 10;
const int* p = &a;
int* const* q = &p;
/*
1. int* const* <= const int**
2. int* <= const int*
3. 错误
*/

对于类里的方法,const对象能调用的,普通对象都能调用

const方法:普通对象和const对象都能调用,因为const this指针,可以指向普通对象,也可以指向const对象

普通方法:const对象不能调用,因为普通this指针不能指向const对象

五、引用和指针的区别

int main() {
int a1 = 10;
int& a2 = a1;
int* p = &a1;

a2 = 100;
*p = 100;
return 0;
}

C和C++的区别(引用&)_c语言_07

指针和引用的区别:

  1. 引用的本质是指针常量,即不可修改指向的指针。引用必须初始化,一旦初始化不可改变引用对象,指针可以不初始化
  2. 引用只有一级,而指针可以多级
  3. 定义一个引用变量和定义一个指针变量,其汇编指令一模一样;通过引用和指针修改变量的值,其汇编指令依然一样
  4. sizeof(引用)得到的是所指向的变量(对象)的大小,而sizeof(指针)得到的是指针本身的大小

class refClass {
private:
long long& ref;
public:
refClass(long long var = 42.0) :ref(var) {}

};

int main() {
long long a = 1;
long long& ref_a = a;
cout << sizeof(ref_a) << endl; // 8,这里sizeof得到的不是引用本身的大小,而是引用对象的大小
cout << sizeof(refClass) << endl; // 4,这里得到的是一个引用的大小
cout << sizeof(long long) << endl; // 8
return 0;
}

参考:​​C++ 引用占用内存?​​

引用数组

int arr[5] = {1,2,3,4,5};
int(&p)[5] = arr; //int(*p)[5] = &arr;

应用的底层使用的是 指针编译时期 在引用被使用的地方,直接替换成指针的解引用

对于引用而言,必须初始化。在编译过后只有在初始化的地方才能看见引用名,其他地方全是以指针解引用的形式出现。如果不初始化,那编译的时候,也无法通过指针解引用的方式替换。

编译时期直接替换的:

  1. 常量的值替换
  2. inline函数
  3. 引用
  4. 宏替换

六、左值和右值

左值: 有内存,有名字,可修改值
右值: 无内存,无名字

// 左值引用error
int& a = 10; // error

// 右值引用,生成值为10的临时量,将临时量的地址给引用a
// 没有被const引用,可修改
int&& a = 10;

// 生成值为100的临时量,将临时量的地址给引用b
// 常引用,被const修饰,不可作为左值
const int& b = 100;

C和C++的区别(引用&)_c语言_08

// 右值引用变量,用于引用右值(不可引用左值)
int&& a = 10;
// 右值引用变量,本身是一个左值,只可用左值引用变量进行引用
int& c = a;

总结:

  1. 右值引用变量,用于引用右值(不可引用左值)
  2. 右值引用变量,本身是一个左值,只可用左值引用变量进行引用
  3. 右值引用常量时,会生成临时量,并将临时量的地址给引用

​std::move​​​:移动语义,得到右值类型
​​​std::forward​​:类型的完美转发,得到真实的左/右值

函数模板的类型推演 + 引用折叠:

  1. ​String&& + && = String&&​
  2. ​String& + && = String&​

//   int tmp = 20; const int& b = tmp;
const int& b = 20; // 常左值引用,无法修改b
// int tmp = 30; int&& c = tmp;
int&& c = 30; // 右值引用,可以修改c
int& e = c; //右值引用变量,本身是一个左值,只可用左值引用变量进行引用

int&& f = std::move(c); // std::move将左值强转为右值,可以被右值引用进行引用

//void construct(T* p, const T& val)//负责对象构造
//{
// new(p)T(val);//定位new
//}
//void construct(T* p, T&& val)//负责对象构造
//{
// new(p)T(std::move(val));//定位new
//}

template<typename Ty>
void construct(T* p, Ty&& val)//负责对象构造
{
new (p) T(std::forward<Ty>(val));//定位new
}



//void push_back(const T& val)//向容器末尾添加元素
//{
// if (full())
// expand();
// //*_last++ = val;//last指针指向的内存构造一个值为val的对象
// _allocator.construct(_last, val);
// _last++;

//}
//void push_back(T&& val)//向容器末尾添加元素
//{
// if (full())
// expand();
// //*_last++ = val;//last指针指向的内存构造一个值为val的对象
// _allocator.construct(_last, std::move(val));
// _last++;

//}

// 函数模板的类型推演 + 引用折叠
// String&& + && = String&&
// String& + && = String&
template<typename Ty>
void push_back(Ty&& val) {
if (full()) {
expand();
}
// forward:类型的完美转发
_allocator.construct(_last, std::forward<Ty>(val));
_last++;
}

当返回值是const,且接收的对象是普通对象时,会构造一个对象返回。接收的对象可以改变
当返回值是普通对象,且接收的是普通对象引用时,报错。因为普通对象引用需要用左值初始化,返回的是右值
当返回值时const,且接收的是对象引用时,普通引用无法接收常对象,对象不可改变。这就达到了const返回值不可修改的目的,使得接收的地方必须用const对象或const引用接收

#include <iostream>
#include <vector>
#include <string>
using namespace std;

string shorterString1(string s1, string s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}

const string shorterString2(string s1, string s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}


string& shorterString3(string& s1, string& s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}

const string& shorterString4(string& s1, string& s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}


int main() {

string s1 = "123";
string s2 = "456";
string s3 = shorterString1(s1, s2);
string s4 = shorterString2(s1, s2);

shorterString1(s1, s2) = "456";
shorterString2(s1, s2) = string("456");

string& s3p = shorterString1(s1, s2);
const string s4p = shorterString2(s1, s2);


string& s5 = shorterString3(s1, s2);
string& s6 = shorterString4(s1, s2);

string s7 = shorterString3(s1, s2);
string s8 = shorterString4(s1, s2);
return 0;
}

指针、引用、const练习题

/*
引用和指针相互转化,移动右侧的&覆盖左边的*即可
*/
// error
int a = 10;
int* p = &a;
const int*& q = p;// 同const int** q = *p const int** <= int**

// error
int a = 10;
int* const p = &a; // int* <= int*
int*& q = p; // 同int** q = &p int** <= int* const*

// error
int a = 10;
const int* p = &a; // int* <= int*
int*& q = p; // 同int** q = &p int** <= const int**

// error
int a = 10;
int* p = &a; // int* <= int*
const int*& q = p; // 同const int** q = &p const int** <= int**


举报

相关推荐

0 条评论