【c++】入门3

发布时间:2024年01月05日

引用

1.swap交换两个变量值的时候可以用引用
2.例题中通过前序遍历数组构建二叉树,可以用引用传别名.


#include <stdio.h>
#include <stdlib.h>
typedef struct BinaryTreeNode {
    char data;
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
} BTNode;
BTNode* BinaryTreeCreate(char* a, int* pi)
{if(a[*pi]=='#')
{   (*pi)++;
    return NULL;}
BTNode* newnode=(BTNode*)malloc(sizeof(BTNode));
newnode->data=a[(*pi)++];
newnode->left=BinaryTreeCreate(a,pi);
newnode->right=BinaryTreeCreate(a,pi);
return newnode;

}
void  InOrder(BTNode* root)
{if(root==NULL)
return ;
InOrder(root->left);
printf("%c ",root->data);
InOrder( root->right);

}

int main() {
    char arr[100];
    scanf("%s",arr);
    int i=0;
   BTNode*bk= BinaryTreeCreate(arr,&i);
    InOrder(bk);
}

i变量是在main函数栈帧中创建的,在调用BinaryTreeCreate(arr,&i);完不会被销毁,可以用引用.
修改如下:

#include <stdio.h>
#include <stdlib.h>
typedef struct BinaryTreeNode {
    char data;
    struct BinaryTreeNode* left;
    struct BinaryTreeNode* right;
} BTNode;
BTNode* BinaryTreeCreate(char* a, int& pi)
{if(a[pi]=='#')
{   pi++;
    return NULL;}
BTNode* newnode=(BTNode*)malloc(sizeof(BTNode));
newnode->data=a[pi++];
newnode->left=BinaryTreeCreate(a,pi);
newnode->right=BinaryTreeCreate(a,pi);
return newnode;

}
void  InOrder(BTNode* root)
{if(root==NULL)
return ;
InOrder(root->left);
printf("%c ",root->data);
InOrder( root->right);

}

int main() {
    char arr[100];
    scanf("%s",arr);
    int i=0;
   BTNode*bk= BinaryTreeCreate(arr,i);
    InOrder(bk);
}

用到引用的地方都是输出型参数


引用做返回值

在这里插入图片描述
注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回。
下面代码输出什么结果?为什么?

#include<iostream>
using namespace std;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

分析:这个程序是不对的,我们刚才说过要使用引用的话,必须是输出型参数,也就是出了作用域不销毁的参数,而这里的c是局部变量,是临时变量,出了作用域要被销毁的。如果传引用回去的话,c地址上的值会被修改。因为c地址上的值随着栈帧的销毁而被修改。这里为什么会是7,因为再次调用同个函数时,用的是和上一个调用的add函数同样的栈帧,所以原地址上的c地址上的值会被新的值7覆盖,于是ret就是7了.

#include<iostream>
using namespace std;
int& Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);
	//Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

如果删除一行,打印出来的值不一定是3,取决于不同的编译器。栈帧中变量空间是否被回收不知道.

传值返回
1.传参返回时不是直接返回,会产生临时变量.
2.静态变量出栈不会被销毁,也产生临时变量.

传引用返回
1.用于出了作用域。不销毁的变量.
2.减少了临时变量的拷贝.
3.调用者可以修改返回对象,比如上面的ret.
4.栈帧销毁,内存消除不确定.
5.出栈帧不存在的变量会出现错误,就不要用引用返回.
6.静态,全局,上一次栈帧,malloc来的变量可以用引用.
7.传引用返回比传值返回提高了效率,不用建立临时变量.


常引用

权限放大

#include<iostream>
using namespace std;

int main()
{
	const int c = 2;
	int& d = c;


}

将只读的变量c,使用引用,d变量的权限不能变成既能读,又能写.权限不能放大.
在举一个指针权限放大的:

#include<iostream>
using namespace std;

int main()
{
	const int* pi = NULL;
	int* p2 = p1;


}

同样也是权限放大,编译器会保错.


权限保持

只读-只读

#include<iostream>
using namespace std;

int main()
{
	const int* pi = NULL;
	const int* p2 = pi;


}

权限缩小

#include<iostream>
using namespace std;

int main()
{
	int x = 1;
	const int& y = x;


}

指针也一样

权限放大,缩小适用于指针和引用.

下面这个可以吗??

#include<iostream>
using namespace std;

int main()
{
	int i = 0;
	 double& rd = i;

}

强制类型转换是将i的值做强制类型转换后的值存在一个临时变量中,而这里的rd是临时变量的别名,而不是i的别名,临时变量具有常性,也就是只读,这里属于权限放大了,加上const的话属于权限平移.

#include<iostream>
using namespace std;

int main()
{
	int i = 0;
	const double& rd = i;

}

引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间.在底层实现上实际是有空间的,因为引用是按照指针方式来实现的.

引用和指针的不同点:

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但有NULL指针
  5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
  6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  7. 有多级指针,但是没有多级引用
  8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  9. 引用比指针使用起来相对更安全

内联函数

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。

内联函数的引出
我们之前学过宏定义函数

#include<iostream>
using namespace std;
#define ADD(a,b) ((a)+(b))
int main()
{
	int ret=ADD(1, 2);
	printf("%d", ret);

}

宏定义的函数不用创建栈帧.
但是宏定义的函数只能是死替换

#include<iostream>
using namespace std;
#define ADD(a,b) (a*b)
int main()
{
	int ret=ADD(1+2, 2+3);
	printf("%d", ret);

}

这样死套就会出错,内联函数的引入,也不会创建栈帧

#include<iostream>
using namespace std;
int add(int a, int b)
{
	int c = a + b;
	return c;

}
int main()
{
	int ret = add(1, 2);
	return ret;


	

}

在这里插入图片描述

对应汇编语言有call指令说明有创建栈帧.


使用inline函数是否创建栈帧
查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不
    会对代码进行优化,以下给出vs2013的设置方式)
    在这里插入图片描述
    使用inline函数后没有call指令,说明没有创建栈帧
    在这里插入图片描述
    inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运行效率。
    inline对于编译器而言只是一个建议,不同编译器关于inline实现机制可能不同,一般建议:将函数规模较小(即函数不是很长,具体没有准确的说法,取决于编译器内部实现)、不是递归、且频繁调用的函数采用inline修饰,否则编译器会忽略inline特性.
    在这里插入图片描述
#include<iostream>
using namespace std;
 inline int add(int a, int b)
{
	int c = a + b;
	c = a + b;
	c = a + b;
	c = a + b;
	c = a*b;
	c = a*b;
	c = a + b;
	c = a + b;
	return c;

}
int main()
{
	int ret = add(1, 2);
	return ret;


	

}

在这里插入图片描述
此函数没有liline展开成函数体,有call指令,创建了函数栈帧.


面试题

宏的优缺点?
优点:
1.增强代码的复用性。
2.提高性能。

缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。

C++有哪些技术替代宏
短小函数定义 换用内联函数

文章来源:https://blog.csdn.net/yyqzjw/article/details/135413579
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。