【c++】入门3

简介: 【c++】入门3

引用

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++有哪些技术替代宏

短小函数定义 换用内联函数

目录
相关文章
|
1月前
|
编译器 C++
C++入门12——详解多态1
C++入门12——详解多态1
38 2
C++入门12——详解多态1
|
1月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
23 3
|
1月前
|
存储 编译器 C语言
C++入门2——类与对象1(类的定义和this指针)
C++入门2——类与对象1(类的定义和this指针)
29 2
|
1月前
|
C++
C++入门13——详解多态2
C++入门13——详解多态2
79 1
|
1月前
|
程序员 C语言 C++
C++入门5——C/C++动态内存管理(new与delete)
C++入门5——C/C++动态内存管理(new与delete)
66 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
19 1
|
1月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
30 1
|
1月前
|
编译器 C语言 C++
C++入门6——模板(泛型编程、函数模板、类模板)
C++入门6——模板(泛型编程、函数模板、类模板)
41 0
C++入门6——模板(泛型编程、函数模板、类模板)
|
1月前
|
存储 安全 编译器
【C++打怪之路Lv1】-- 入门二级
【C++打怪之路Lv1】-- 入门二级
23 0
|
1月前
|
自然语言处理 编译器 C语言
【C++打怪之路Lv1】-- C++开篇(入门)
【C++打怪之路Lv1】-- C++开篇(入门)
24 0