【C++】C++入门(下)

简介: 【C++】C++入门(下)

引用

什么是引用?

 引用是给一个已经存在的变量取一个别名,在语法上并不会给这个别名开一个空间,它和她引用的变量共用一个空间。但是实际上引用也是开了一块空间的,用来存放引用名。引用是按照指针的方式来实现的。

引用语法:

 类型& 引用变量名(对象名/别称) = 引用实体;注意引用类型必须和被引用类型相同。

int main()
{
  int a = 10;
  int& b = a;
  printf("%p\n", &a);
  printf("%p\n", &b);
  return 0;
}

9c1943bc7261418faeef5bfb43bf069d.png

  1. 引用特性:
      引用在定义时必须初始化,引用必然是有被引用对象的,不可能说先取个别名,但是不知道这个别名是谁;
      一个变量可以有多个引用,相当于一个人可以有很多个外号一样;
      引用一旦引用一个实体,就不能再引用其他实体了。
int main()
{
  int a = 10;
  //int& b;这个会直接报错
  int& c = a;
  int& d = a;
  int e = 20;
  c = e;
  printf("%p\n%p\n%p\n%p\n", &a, &c, &d, &e);
  return 0;
}

ca67ab45a2514e9fb7d92b064e7e94d7.png

如果被引用对象是一个常数,则需要添加const修饰。

int main()
{
  const int a = 10;
  const int& b = a;
  const int& c = 20;
  return 0;
}
  1. 使用场景:
      做参数
void swap(int* a, int* b)
{
  int x = *a;
  *a = *b;
  *b = x;
}
void swap(int& a, int& b)
{
  int x = a;
  a = b;
  b = x;
}
int main()
{
  int a = 1;
  int b = 2;
  swap(&a, &b);
  cout << a << " " << b << endl;
  swap(a, b);
  cout << a << " " << b << endl;
  return 0;
}

7addb23bc2a141108a6afb11f4e8755f.png

做返回值,可以减少拷贝;调用者可以修改返回值。

 需要提前知道一个点,一个函数的返回值实际上是这个值的拷贝,而不是这个值本身。

 如果函数返回时,出了函数作用域,如果返回对象还没有还给系统,则可以使用引用返回,如果已经还给系统了,则不能使用引用返回,如果使用结果是返回值是未定义的。


int& Count()
{
  static int n = 0;
  n++;
  return n;
}
int main()
{
  int ret = Count();
  return 0;
}

e9a2be2d18e3404684334ea99f412a76.png


  1. 权限问题:
      指针和引用可以进行权限平移和缩小,但是不能进行权限放大。
int main()
{
  int a = 1;
  const int b = 2;
  //权限平移
  int& ra = a;
  const int& rb = b;
  //权限缩小
  const int& c = a;
  //权限放大
  int& d = b;
  return 0;
}


7e713ab1f03047359fa72051b80ec794.png

与指针的区别

 引用在概念上是一个变量的别名,指针存储一个变量地址;

 引用必须初始化,指针没有规定;

 引用在初始化后就不能改变了,指针可以在任何时候改变指向;

 在sizeof中含义不同,引用的结果是引用类型的大小,但是指针始终是地址空间所占用的字节个数;

 引用自加是被引用的实体加一,指针自加是指针向后偏移一个类型的大小;

 指针可以有多级,但引用没有;

 访问实体时,指针需要显式的解引用,引用是编译器自己处理;

 引用比指针更加安全。


内联函数

概念:

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

int Add(int left, int right)
{
  return left + right;
}
inline int Sub(int left, int right)
{
  return left - right;
}
int main()
{
  int ret = 0;
  ret = Add(1, 2);
  int tmp = 0;
  tmp = Sub(4, 3);
  return 0;
}

有call说明进行了跳转,是开辟了栈帧空间的。

ce4fc58ff8544fd9a7b47fe5ba40be55.png

我们默认的调试模式是在Debug模式,Debug是不会进行代码优化的,如果我们想要看到内联函数的展开就需要修改一些配置。Release又对代码优化的太厉害了,我们不太容易看懂。

43f0c176a7914d229a33c796e5264c46.png


7047d53fb113404a8bfce4556d05f8f8.png

c48640bb6e244ba098705094d392e046.png

特性:

 内联函数是一种空间换时间的做法,如果编译器将函数当做内联函数处理,在编译阶段,会用函数体替换函数调用。这是就会出现一个问题,如果内联函数太大,就会使目标文件变大,所以内联函数常被用来写一些短小的函数。实际上,如果内联函数过长,编译器也会忽视这个内联,把它当做普通函数处理;

 内联函数对于编译器只是一个建议,不同的编译器关于内联函数的实现机制可能存在不同。在《C++ prime》中明确指出,内联说明只是想编译器发出一个请求,编译器可以选择忽略这个请求;

 内联函数的声明和定义一般情况下是不分离的,因为分离之后可能会导致链接错误,内联函数是会被展开的,展开之后就不存在函数地址了,那这个时候去链接地址就会找不到。

 

使用场景:

 内联函数常被用来替代宏函数。这时候就有个问题,既然有宏函数,为什么又要搞出内联函数这个东西?因为宏是较大缺陷的。

 宏的优缺点:

   优点:增强代码的复用性;提高性能。

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

   所以在C++中,常使用const enum代替宏的常量定义,用内联函数代替宏函数。

auto关键字(C++11):

  在写代码的时候,可能会遇到一个类型名非常长的情况,这种类型名写一两个还行,但是如果多了就非常容易写错。有些人会用typedef来解决这一问题,但是typedef有一个缺陷。


e79c313a91d14b589d95081bf8d1d1d6.png

 早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但是并没有人使用它。之后在C++11中,auto就具有新的含义了,auto不再是一个存储类型的指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

 auto定义变量时必须初始化,auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器编译时会将auto替换为变量实际的类型。

 用autto声明指针类型时,用auto和auto*没有任何区别,但是用auto声明引用类型时必须要加&。

int main()
{
  int x = 10;
  auto a = &x;
  auto* b = &x;
  auto& c = x;
  auto d = x;
  //typeid().name()可以返回变量的类型名
  cout << typeid(a).name() << endl;
  cout << typeid(b).name() << endl;
  printf("%p\n", &x);
  printf("%p\n", &c);
  printf("%p\n", &d);
  return 0;
}

ac0f855a77004e0da53e0e05abadf11e.png

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

a3daf856cdda45d3bbc07f0e108d24ca.png

 auto不能作为函数参数;

 auto不能直接用来声明数组;

 为了避免与C++98中的auto发生混淆,C++11值保留了auto作为类型指示符的用法;

基于范围的for循环(C++):

  C++11中引入了基于范围的for循环。for循环后的括号由冒号":"分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

int main()
{
  int a[] = { 1,2,3,4,5 };
  //自动依次取数组中的数据赋值给e(e只是个名字,可以改变),自动判断结束
  for (auto e : a)
    e *= 2;
  for (auto e : a)
    cout << e << " ";
  cout << endl;
  //这里相当于e是a[0]、a[1]等的别名,就可以达到改变数组的效果
  for (auto& e : a)
    e *= 2;
  for (auto e : a)
    cout << e << " ";
  cout << endl;
  return 0;
}

8572078134d346f8aef3a3644555f4ba.png

与普通循环类似,也可以使用continue和break;

 范围for的循环范围必须是确定的;

指针空值nullptr(C++11):

 NULL实际是一个宏定义的,NULL可能被定义为字面常量0,或者被定义为无类型指针(void *)的常量。也不知道出于什么原因被定义成了字面常量0,这样在程序中就会造成一些不可预料的问题。

097dce8ac21841418542f0ae17417bce.png

 于是C++11就打了一个补丁,引入nullptr作为关键字,代表指针空值。在C++11中,sizeof(nullptr)与sizeof((void*)0)所占用的字节数相同。


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