C++基础部分

C++基础

主要C++解决C语言的一些缺陷。

2.命名空间

  1. 命名空间里面可以定义变量、函数、类型
  2. 命名空间可以嵌套
  3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。

命令冲突问题:

  1. 我们自己定义的变量,函数可能与库里面重名冲突
  2. 多人协作代码的时候两个人之间的代码命名会冲突
#include<stdio.h>
#include<stdlib.h>
//命名冲突问题
int rand=0;
int main(){
    
    
    printf("%d\n",rand);
	return 0;
}

CPP如何解决?CPP提出了命名空间

#include<stdio.h>
#include<stdlib.h>

namespace YCB{
    
    
    int rand=0;
}
int main(){
    
    
    printf("%d\n",rand);
    printf("%d\n",YCB::rand);
    return 0;
}
  • 编译器优先在局部找,找不到再去找全局的
#include<stdio.h>
#include<stdlib.h>

namespace YCB{
    
    
    int rand=0;
}
int a=0;
int main(){
    
    
	int a=1;
    printf("%d\n",::a);///在全局域里面找,C语言就有
    printf("%d\n",a);//编译器优先在局部找,找不到再去全局的
    return 0;
}
  • 命令空间里面的一样还是全局变量,放在静态区
#include<stdio.h>
#include<stdlib.h>
//定义了一个命名空间,他们就是全局变量,性质和全局变量一模一样。看成全局变量即可。不过套了一下壳子.全局变量当然是直接初始化了。
namespace YCB{
    
    
    int rand=0;
    int a=1;
}
void f(){
    
    
    //局部域
}
int a=0;
int main(){
    
    
	int a=1;
    printf("%d\n",::a);///在全局域里面找,C语言就有
    printf("%d\n",a);//编译器优先在局部找,找不到再去全局的
    printf("%d\n",YCB::a);
    return 0;
}
  • 命名空间里面可以定义变量、函数、类型

每个命名空间之间可以定义同名函数,不会冲突

namespace YCB{
    
    
    int a=10;
    int b=20;
    int Add(int a,int b)
    {
    
    
        return a+b;
    }
	int Sub(int a,int b)
    {
    
    
		return a-b;
    }
    struct Node{
    
    
        struct Node *next;
        int val;
    }
}///不需要;
int main(){
    
    
    struct YCB::Node node;
}
  • 命名空间之间可以嵌套
namespace YCB
{
    
    
    int a,b;
    int add(int a,int b) return a+b;
    namespace S{
    
    
        int c,d;
        itn sub(int c,int d) return c-d;
    }
}
  • 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
//List.h
#pragma once

namespace YCB{
    
    
    Struct ListNode{
    
    
        
    }
    void ListInit();
    void ListPushBack();
}
//List.cpp
#include"List.h"
namespace YCB{
    
    
    void ListInit(){
    
    
        ...
    }
    void ListPushBack(){
    
    
        ...
    }
}
//两者会合到一起

2.1三种使用方式

  • 加命名空间名称及作用域限定符

    • 指定作用域能做到最好的命名隔离,但是使用不方便

    • int main()
      {
              
              
      	printf("%d\n",N::a);
          return 0;
      }
      
  • 使用using将命名空间中成员引入

    • 用于展开命名空间中常用的。

    • using N::b;
      int main()
      {
              
              
      	printf("%d\n",N::a);
          printf("%d\n",b);
      }
      
  • 使用using namespace 命名空间名称引入

    • 全部展开到全局了,隔离失效

    • 正规做项目慎用

    • using namespace N;
      int main()
      {
              
              
      	printf("%d\n",N::a);
          printf("%d\n",b);
          Add(10,20);
      }
      
#include<iostream>
using namespace std; //c++库中所有东西都是放到std命名空间的
///全开展,好处方便,但是如果定义了自己的函数和std同名了就不行
int main()
{
    
    
    
    
}

3.c++输入&输出

#include<iostream>
//在日常练习中,不在乎跟库命名冲突
using namespace std; //c++库中所有东西都是放到std命名空间中

int cout=10;//跟库里面冲突
//但是非要定义,一种解决是std::cout,std::endl;
//另一种只展开部分 using std::cout ,using std::endl;

//常用的库里面一些对象或者类型展出来
//项目当中比较常见
using std::cout;
using std::endl;
int main()
{
    
    
    std::cout<<"hello world\n";
    std::cout<<"hello world"<<std::endl;
    //自动识别类型:通过函数重载实现
    int i=1;double d=1.11;
    std::cout<<i<<" "<<d<<std::endl;
}

4.缺省参数

缺省参数是声明或定义函数时为参数提供的一个默认值。

void Func(int a=0)//形参的缺省参数-->备胎
{
    
    
    cout<<a<<endl;
}
int main()
{
    
    
    Func(10);
    Func();
    return 0;
}

4.1全缺省

///全缺省
void Func1(int a=10,int b=20,int c=30)
{
	cout<<"a="<<a<<endl;
	cout<<"b="<<b<<endl;
    cout<<"c="<<c<<endl;
}
int main()
{
	Func1();
    Func1(1);
    Func1(1,2);
    Func1(1,2,3);
}

4.2半缺省

//半缺省(缺省部分参数),必须从右往左连续缺省
void Func2(int a,int b=10,int c=20)
{
    
    
	Func2();//error
    //调用时如果要传参必须从左往右依次传参。
    Func2(1);
    Func2(1,2);
    Func2(1,2,3);
}
void StackInit(struct Stack* ps,int capacity=4){
    
    
    ps->a=(int*)malloc(sizeof(int)*capacity);
    //
    ps->top=0;
    ps->capacity=capacity;
}

4.3缺省的注意事项

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给

  2. 缺省参数不能在函数声明和定义中同时出现

    1. //a.h
      void TestFunc(int a = 10);
      // a.c
      void TestFunc(int a = 20)
      {
              
              }
      // 注意:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值
      
  3. 缺省值必须是常量或者全局变量

5.函数重载

5.1函数重载概念

5.1.1重载的判定

一个函数有多种意义或者多个调用方式

  • 函数名相同
  • 参数不同(满足一个)
    • 类型
    • 个数
    • 类型顺序
      • void func(int i,int j) -->_Z4funcii
      • void func(int j,int i) -->_Z4funcii
      • 两者不是重载
  • 对返回值没有要求,返回值和重载没有关系
int Add(int l,int r)
{
    
    
    return l+r;
}
double Add(double l,double r)
{
    
    
    return l+r;
}
long Add(long l,long r)
{
    
    
    return l+r;
}
void func1(int i,char ch)
{
    
    
    
}
void func1(char ch,int i)
{
    
    
    
}
void func1()
{
    
    
    
}
int func1()//和上一个不构成重载
{
    
    

}
int main()
{
    
    
    Add(10,20);
    Add(10.0,20.0);
    Add(10L,20L);
    return 0;
}

5.1.2调用存在歧义

void f(){
    
    
     cout<<"f()"<<endl;
}
void f(int a=0){
    
    
     cout<<"f(int a)"<<endl;
}
int main(){
    
    
    f();//error:调用存在歧义
}

5.2重载的实现(名字修饰)

  1. 什么是函数重载
  2. c++是如何支持函数重载的?为什么C语言不支持

得从编译链接说起

list.h list.c test.c

  1. 预处理(预编译):头文件的展开/宏替换/条件编译/去掉注释
    1. list.i test.i
  2. 编译 :检查语法,生成汇编代码
    1. list.s test.s
  3. 汇编 :汇编代码转换成二进制的机器码
    1. list.o test.o
  4. 链接 :将两个目标文件链接到一起生成可执行程序(如果当前文件中有函数的定义,那么编译时就填上地址了;如果在在当前文件中只有函数的声明,只能链接的时候去其他的xxx.o符号表中根据函数修饰名字去找,这就是链接的重要工作
test.c

void list_push_back(int x);
void add(int i,int j);
int main()
{
    
    
    list_push_back(1);
    return 0;
}
list.c -->list.o
void list_push_back(int x);
void add(int i,int j);
void list_push_back(int x)
{
    
    
    printf("%d\n",x);
}
void add(int i,int j)
{
    
    
    
}
test.o
    ...
    call list_push_back(?)
    //链接时,这里的问号表示编译时,这个函数我们只有声明,没有定义所以无法找到他的地址
    //表示链接的时候,到其他的目标文件的符号表中去找这个函数的地址
    ...
符号表:
    main:0x31141321
list.o
    list_push_back()
{
    
    
    ...
}

符号表:
list_push_back:0x31144332
add:0x3114432

  • C语言是直接用函数名称。两个都是一样的函数名字,无法区分。

因为C++是名字修饰。所以可以。

0000000000000400623<_Z3addii>
   
000000000000040062f<_Z3adddd>
    
   test.o
//   add(1,2);
   call _Z3addii(?)
//   add(1.1,2.2);
   call _Z3adddd(?)
  • C++的函数修饰规则(不同编译器不同规则),但都把参数类型加进去了

_Z+函数长度+函数名+类型首字母

image-20211125210846252

image-20211125211121715

因此函数命名不同了之后链接的时候去其他目标文件中找符号表就能找到对应的函数。

//add(1,2)
call _Z3addii(?)  //link的时候去找?的地址
    //其他目标文件中_Z3addii符号的地址是000000004007dd
//add(1.1,2.2)
call _Z3adddd(?)  //link的时候去找?的地址
    //其他目标文件中_Z3adddd符号的地址是000000004007ff

image-20211125212731369

image-20211125215458388

C语言不支持函数重载,因为编译的时候,两个重载函数,函数名相同,在同一个目标文件.o中,符号表中存在歧义和冲突。同样的函数名两个函数地址。其次链接的时候也存在歧义和冲突,因为他们都是直接使用函数名去标识和查找。而重载函数,函数名相同。

而C++的目标文件符号表中不是直接用函数名来标识和查找函数

  1. 函数名修饰规则(不同编译器下函数修饰名不同)
    1. _Z+函数长度+函数名+类型首字母
      1. image-20211125220352038
  2. 有了函数名修饰规则,只要参数不同,目标文件的符号表里面就不存在二义性和冲突了
  3. 链接的时候,test.o的main函数里面去调用两个重载的函数也是明确的。

5.3extern “C”

对于c++的静态库、动态库,C程序无法找到,因为函数名字在其符号表里找不到。

image-20210828153610461

如何要求C和C++程序都能够用这个C++的(静态库、动态库)

当C的程序和C++程序都想去调用C++的库。

image-20210828154234564

6.引用

6.1引用概念

#include<iostream>
using namespace std;
int main()
{
    
    
	int a=1;
    int& ra=a; //ra是a的引用,引用也就是别名。a再取了一个名称ra
	int& raa=ra;
}

引用在物理空间上的意义:

image-20210828154708086

引用就是给一个变量再取新的名字。编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块内存空间

类型名& 引用变量名(对象名)=引用实体

6.2引用特性

  1. 引用必须在定义时初始化

    1. int a=1;
      int& b;///error
      
  2. 一个变量可以有多个引用

  3. 引用一旦引用一个实体,不能发生变化

    //1.引用必须在定义时必须初始化
    int main(){
          
          
        int a=10;
        int &b;
    }
    
    //2.一个变量可以有多个引用
    int main(){
          
          
    	int a =10;
    	int&b =a;
    	int&c =a;
    	int&d =b;
    }
    
    //3.引用一旦引用一个实体,再不能引用其他实体
    int main(){
          
          
        
    	int a=10;
    	int &b=a;
    	int c=20;
    
    	b=c;///分析:这里是c变成了d的引用?还是d赋值给c(yes)
        
     	   
    }
    

    6.3常引用

    总结:

    引用取别名时,变量访问权限可以缩小,不能放大。

    权限的放大和缩小规则:适用于引用和指针。不适用变量之间的赋值。

int main()
{
int a=0;
int& b=a; //b的类型是int

const int a=0;
int&b = a;//error:编译不通过。原因:a是const,但是不能修改,b的类型是int,也就是可读可写,那么逻辑上就会产生矛盾。
const int& b=a;//right


int c=1;
int& d=c;
const int& e =c;行不行?可以->c是可读可写的,e变成别名是只读,逻辑上是可以的。


int i=0;
double db=i; //隐式类型转换
double& rdb=i;//error
float& rf=i;//error:和字节大小无关
//但是+const就可以
const double& rd=i;
const float& rf=i;



//变量之间赋值没有权限缩小和放大的关系,引用才有
const int ci =i ;
int x=ci;

return 0;

}


隐式类型转换的赋值是怎么产生的?

![image-20210828160751889](https://img-blog.csdnimg.cn/img_convert/9e91507cc609fb07d717a7b7b4bc25f3.png)

```cpp
int main()
{
 const int* cp1=&a;
 int* p1=cp1;//error: const int* cp1表示cp1指向的内容不能更改。而int* p1=cp1如此赋值表明p1可以修改该块内存。逻辑错误。权限的放大。
 
 int* p2=&c;
 const int* cp2=p2; //权限缩小,ok
}

不用引用传参无所谓。const对象拷贝给x。

void f(int x){
    
    
    cout<<x<<endl;
}
int main(){
    
    
	const int a=10;
    const int &b =a;
    f(a);
    f(b);
}
void f(int &x){
    
    
    cout<<x<<endl;
}
int main(){
    
    
    const int a=10;
    const int &b=a;
    f(a);//error:权限的放大。
    f(b);
}
void f(const int& x){
    
    
    cout<<x<<endl;
}
int main(){
    
    
    const int a=10;
    const int&b=a;
    f(a);
    f(b);
}

6.4引用场景

1、引用做参数

  • 输出型参数
  • 提高效率

回顾之前单链表的PushBack部分,我们要注意传递二级指针来处理原来指针变量的值。

void SLiPushBack(STLNode** pphead,SLTDataType x){
    
    
 	assert(pphead);
    
    STLNode* newnode=CreateSListNoded(x);
    if(*pphead==NULL){
    
    
        *pphead=newnode;
        return;
    }
    else{
    
    
        STLNode* tail=*pphead;
        while(tail->next!=NULL) tail=tail->next;
        tail->next=newnode;
    }
}
int main(){
    
    
    SLTNode* plist=NULL;
    SListPushBacn(&plist,1);
    SListPushBacn(&plist,2);
    SListPushBacn(&plist,3);
    SListPushBacn(&plist,4);
    
    SListPushBack(plist);
    return 0;
}

有了引用之后,就可以省去一层二级指针。

int main(){
    
    
    
	int a=10;
	int& b=a;

	int *p1=&a;
	int *&p2=p1;
}
void SLiPushBack(STLNode*& pphead,SLTDataType x){
    
    
 	assert(pphead);
    
    STLNode* newnode=CreateSListNoded(x);
    if(pphead==NULL){
    
    
        pphead=newnode;
        return;
    }
    else{
    
    
        STLNode* tail=pphead;
        while(tail->next!=NULL) tail=tail->next;
        tail->next=newnode;
    }
}
int main(){
    
    
    SLTNode* plist=NULL;
    SListPushBacn(&plist,1);
    SListPushBacn(&plist,2);
    SListPushBacn(&plist,3);
    SListPushBacn(&plist,4);
    
    SListPushBack(plist);
    return 0;
}

再比如做C语言的题的时候给定接口的int* returnSize就是一个输出型参数。

void swap_c(int* p1,int *p2)
{
    
    
    int tmp=*p1;
    *p1=*p2;
    *p2=tmp;
}
void swap_cpp(int &r1,int &r2)
{
    
    
	int tmp=r1;
    r1=r2;
    r2=tmp;
}
int main()
{
    
    
    int a=0;int b=1;
    swap(&a,&b);
    swap_cpp(a,b);
    return 0;
}

前面说到引用定义的时候要初始化,这里引用定义的地方在传参。

2、引用做返回值

总结:

  1. 凡是传值,不管是参数还是址,都会产生拷贝变量。传引用不会。
  2. 一个函数要使用引用返回,返回变量出了这个函数的作用域还存在,就可以使用引用返回,否则不安全。
    1. 全局变量、静态变量等
  3. 函数使用引用返回的好处是什么
    1. 少创建拷贝一个临时对象,提高效率。
    2. 其实还有一个作用,以后再补充。
    3. 修改返回对象如operator[] (已补充–模板初阶模板类)

先来回顾一下传值返回。

所有的传值都会生成一个拷贝

int Add(int a,int b)
{
    
    
	int c=a+b;
    return c;
}
int main(){
    
    
    int ret=Add(1,2);
    cout<<ret<<endl;
    return 0;
}

image-20211128151853981

我们可以看到调用Add(int,int)函数的过程return c的过程中,将计算出来的c变量的值存到了临时变量%eax寄存器中,然后再传给main函数中的ret变量。

临时变量存在哪里呢?

  1. 如果c如果比较小(4 or 8),一般是寄存器充当临时变量。
  2. 如果c比较大,临时变量放在调用Add函数的栈帧中。

而传引用返回就是不会生成c的拷贝返回,直接返回c的引用

int Add1(int a,int b)
{
    
    
    int c=a+b;
    return c;
}
int main()
{
    
    
    const int& ret=Add1(1,2);//临时变量具有常性
    Add1(3,4);
    cout<<"Add1(1,2) is:"<<ret<<endl;
}
int& Add2(int a,int b)
{
    
    
    int c=a+b;
    return c;
}
int& Add2(int a,int b)
{
    
    
    static int c=a+b;
    return c;
}
int main()
{
    
    
    int& ret=Add2(1,2);//ret就是c的别名。(实际上是c这块空间的别名).
    //销毁不意味着清除,是没有使用权。
    Add2(3,4);
    cout<<"Add2(1,2) is:"<<ret<<endl;///ret输出为7了。引用返回是不安全的。
    //说明如果返回变量c是一个局部变量时,引用返回是不安全的。
    return 0;
}
img

出了作用域还是返回已经销毁的栈帧(未有使用权)。不能保证原来的结果,其他函数能改这一块的。就会产生问题。

如何解决这个问题?

加static。

void test()
{
    
    
    static int a=1;///第二次不执行
    a++;
    printf("%d",a);
}

此时c不在Add2的栈帧。

所以第二次Add(3,4)的时候static int c=a+b是不执行的。这份代码是只能是3。不过只有本函数才能改自己的c。

int Count1()//传值返回
{
    
    
    static int n=0;
    n++;
    return n;//返回临时变量
}
int& Count2()//传引用返回
{
    
    
	static int n=0;
    n++;
    return n;//没有额外空间
}
int main()
{
    
    
	int& r1=Count1();//error:r1想成为临时变量的别名,因为临时变量具有常性。所以不行。需要加const
    int& r2=Count2();//tmp相当于n的别名。r2相当于tmp的别名。
    return 0;
}

image-20210828194342670

image-20210828194442954

6.5传值,传引用的效率比较

#include<ctime>
struct A{
    
    
    int a[10000];
};
A a;
A TestFunc1() {
    
    return a;}
A& TestFunc2() {
    
     return a;}

void main()
{
    
    
	size_t begin1=clock();
    for(size_t i=0;i<10000;i++)
    {
    
    
        TestFunc1();
    }
    size_t end1=clock();
    cout<<end1-begin1<<endl;
    size_t begin2=clock();
    for(size_t i=0;i<10000;i++)
    {
    
    
        TestFunc2();
    }
    size_t end2=clock();
    cout<<end2-begin2<<endl;
}
#include<ctime>
struct A{
    
    
    int a[10000];
};
void TestFunc1(A a) {
    
    return a;}
void TestFunc2(A& a) {
    
     return a;}

void main()
{
    
    
    A a;
    //以值作为函数参数
	size_t begin1=clock();
    for(size_t i=0;i<10000;i++)
    {
    
    
        TestFunc1(a);
    }
    size_t end1=clock();
    
    //以引用作为函数参数
    size_t begin2=clock();
    for(size_t i=0;i<10000;i++)
    {
    
    
        TestFunc2(a);
    }
    size_t end2=clock();
}

总结一下:引用的作用主要体现在传参和传返回值。

  1. 引用传参和传返回值,有些场景下面,可以提高性能。(大对象+深拷贝对象)
  2. 引用传参和传返回值,输出型参数和输出型返回值。通俗点说,有些场景下面,形参的改变可以改变实参。

有些场景下面,引用返回,可以改变返回对象。

6.6引用和指针的区别

在语法概念上引用就是别名,没有独立空间,和其引用实体共享一个空间。

在底层实现(看反汇编)上,是和指针一样的。

引用和指针的不同点

  1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
  2. 引用在定义时必须初始化,指针没有要求
  3. 引用在初始化引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  4. 没有NULL引用,但是有NULL指针。
  5. 在sizeof中含义不同:引用的结果是引用9类型的大小,指针的结果是地址的大小。
  6. 有多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式应用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全
  9. image-20210830195308689
#include<iostream>
using namespace std;
int main()
{
    
    
	int a=10;
    int& b=a;
    
    int* p =&a;
    
    return 0;
}
00000000000007aa <main>:
 7aa:	55                   	push   %rbp
 7ab:	48 89 e5             	mov    %rsp,%rbp
 7ae:	48 83 ec 20          	sub    $0x20,%rsp
 7b2:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
 7b9:	00 00 
 7bb:	48 89 45 f8          	mov    %rax,-0x8(%rbp)
 7bf:	31 c0                	xor    %eax,%eax
     
     
 7c1:	c7 45 e4 0a 00 00 00 	movl   $0xa,-0x1c(%rbp)
     
     
 7c8:	48 8d 45 e4          	lea    -0x1c(%rbp),%rax
 7cc:	48 89 45 e8          	mov    %rax,-0x18(%rbp)
     
 7d0:	48 8d 45 e4          	lea    -0x1c(%rbp),%rax
 7d4:	48 89 45 f0          	mov    %rax,-0x10(%rbp)
     
 7d8:	b8 00 00 00 00       	mov    $0x0,%eax
 7dd:	48 8b 55 f8          	mov    -0x8(%rbp),%rdx
    
     
 7e1:	64 48 33 14 25 28 00 	xor    %fs:0x28,%rdx
 7e8:	00 00 
 7ea:	74 05                	je     7f1 <main+0x47>
 7ec:	e8 7f fe ff ff       	callq  670 <__stack_chk_fail@plt>
 7f1:	c9                   	leaveq 
 7f2:	c3                   	retq   

7.内联函数

一般情况下在Debug下不能展开。但是Release看不到。

需要VS设置。通过反汇编就可以看到没有Call了。

image-20210830195203676

int Add(int left,int right)
{
    
    
  return left+right;  
}
void Swap(int &x1,int &x2)
{
    
    
    int tmp=x1;
    x1 =x2;
    x2 =tmp;
}
///频繁调用Swap是有栈帧消耗的
//C语言如何解决:1.C语言使用宏函数(提前展开了)2.C++使用内联函数(会在调用的地方展开)
int main()
{
    
    
    int ret=Add(1,2);
}
  1. inline函数是一种空间换时间的做法,省去调用函数额外开销。
    1. Call Swap 假设程序中调用了1w次。假设swap10行指令。此时是10010
    2. inline之后就没有调用的call。但是展开后指令个数是100000了。
    3. 一般内联适用于小函数,小于20行。其次递归,长的代码不适用于内联。
  2. inline对于编译器而言只是一个建议,编译器会自动优化。
  3. 内联不建议声明和定义分离,分离会导致连接错误。因为inline被展开,就没有函数地址了,链接就找不到。

面试题

  • 宏的优缺点
    • 优点:
      • 增强代码的复用性
      • 提高性能
    • 缺点
      • 不方便调试宏(因为预编译阶段进行了替换)
      • 导致代码可读性差,可维护性差,容易误用
      • 没有类型安全的检查
  • C++的替代
    • 用const替换常量定义
    • 短小函数定义换用内联函数

8.auto关键字(C++11)

8.1auto简介

int main()
{
    
    
    int a=10;
    auto b=a;//b的类型是根据a的类型推导出是int
}

观察类型的函数:typeid(a).name()

int a=0;
auto b=a;
int& c=a;
auto& d=a;
auto* e=&a;//int*
auto f=&a;//int*
cout<<typeid(a).name()<<endl;//int
cout<<typeid(b).name()<<endl;//int
cout<<typeid(c).name()<<endl;//int
cout<<typeid(d).name()<<endl;//int
cout<<typeid(e).name()<<endl;//int*
cout<<typeid(f).name()<<endl;//int*

8.3auto不能推导的场景

  1. auto不能作为函数的参数
  2. auto不能直接用来声明数组
  3. 为了避免和C98的auto发生混淆,C11只保留了auto作为类型指示符的用法
  4. auto在实际中最常用的优势用法就是新式for循环和lamabda表达式

9.基于auto的范围for

int main()
{
    
    
    int array[]={
    
    1,2,3,4,5};
    for(int i=0;i<sizeof(array)/sizeof(int);++i)
    {
    
    
        array[i]*=2;
    }
    for(int i=0;i<sizeof(array)/sizeof(int);++i){
    
    
        cout<<array[i]<<" ";
    }
    cout<<endl;

	//c++11 -->范围for-->特点:写起来比较简洁
    for(auto &e:array)//实际上是把array的值取出来赋值给e。所以要修改要引用。
    {
    
    
        e*=2;
    }
    for(auto e:array)
    {
    
    
        cout<<e<<" ";
    }
    cout<<endl;
}


void TestFor(int array[])//还记得C基础吗,传数组名就是数组元素首地址,此时退化成了指针。
{
    
    
    for(auto& e:array)//error:此刻的array不是数组
    {
    
    
        cout<<e<<endl;
    }
}

10.指针空值nullptr

C++中#define NULL 0 。

void fun(int n)
{
    
    
    cout<<"整型"<<endl;   
}
void fun(int* p)
{
    
    
    cout<"整形指针"<<endl;
}
int main()
{
    
    
    //C
    int* p1=NULL;
    
    //C++11,推荐像下面这样去用
    int* p2=nullptr;
    
    fun(0);
    fun(NULL);//跳转的是第一个fun. fun(0)
    fun(nullptr);// fun( (void*)0 )
}

猜你喜欢

转载自blog.csdn.net/zstuyyyyccccbbbb/article/details/121592508