6、数组、指针与引用


一、为什么要引入数组?

  • 数组是由一组具有 相同数据类型 的元素按照一定的规则组成的集合,用 数组名 表示存储区域的地址,用 下标变量 来标识数组中的每个元素
  • 用数组类型可以描述许多有意义的对象,便于处理同一性质的成批数据,如向量、矩阵等
  • 数组和指针有着密切的联系,任何能由数组下标完成的操作也都可以用指针来实现
  • 数组分类: 按照数组元素类型可分为:数值数组、字符数组、指针数组、结构数组等;按照维数可分为:一维数组、二维数组、多维数组等

二、一维数组(对应一个线性表)

  • 一维数组的定义、存储和初始化
    • 定义:数据类型 数组名[整型常量表达式];
    • 存储:数组名/&数组名[0] 存放该数组的首地址,是一个地址常量(占用内存大小 sizeof(数组名)
    • 初始化:在定义数组的同时,为数组元素赋初值,数据类型 数组名[整型常量表达式] = {常数1, 常数2, ... , 常数n};
      • 此时整型常量表达式可省略,数组长度由初值个数确定
      • 当整型常量表达式的值大于初始化值的个数时,其余的元素系统自动赋 0
      • 全局数组若不初始化,编译器将其初始化为 ;局部数组若不初始化,内容为 随机值
      • eg:int myNumbers[5] = {0}; // initializes all integers to 0
  • 数组元素的引用
    • 下标方式:数组名[下标]
    • 地址方式 :*(地址)
    • 指针方式:*指针变量名
  • 数组排序:选择法、冒泡法、插入法、快速排序法
  • 求取数组元素的个数:
    • C++ 中的 std::size 方法:std::size(ptr)
    • C 和 C++ 中通用的 sizeof 方法:sizeof(ptr)/sizeof(type)
    • C++ 中的 (c)end - (c)begin 方法:std::end(ptr) - std::begin(ptr)
  • 数组元素的基本操作
    // 假设有定义:int j, a[N]; 
    // 数组元素的输入
    for (j=0; j<N; j++)
    {
    	cin >> a[j];
    }
    
    // 数组元素的输出
    for (j=0; j<N; j++)
    {
    	cout << a[j] << " ";
    }
    
    // 数组元素的求和
    sum=0;
    
    for (j=0; j<N; j++)
    {
    	sum += a[j];
    }
    
    // 求数组中的最小元素
    min=a[0];                 //假设第一个元素值最小
    
    for (j=1; j<N; j++)
    {
    	if (a[j] < min)
    	{
    		min = a[j];
    	}
    }
    
    // 求数组中的最小元素下标
    imin=0;                 //假设 0 为最小元素下标
    
    for (j=1; j<N; j++)
    {
    	if (a[j] < a[imin])
    	{
    		imin = j;
    	}
    }
    
    

三、二维数组(相当于一个矩阵)

  • 二维数组的定义和初始化
    • 定义:数据类型 数组名[常量表达式1][常量表达式2];
    • 初始化:
      这里写图片描述
  • 二维数组的基本操作同一维数组相似, Note:任何维数的数组在内存中都是以线性表的形式连续存放的:
    在这里插入图片描述

四、字符数组

1、C 风格字符串

  • 字符串的特点:
    • C 中没有专门的 字符串变量,但提供了 字符数组和字符指针 来处理字符串的方法
    • C 中的字符串是以'\0' 结尾的字符数组,若不以'\0' 结尾,则不是一个字符串,只是普通字符数组
    • 内存的首地址 来代表该字符串
  • 字符串的三种形式:
    • 字符数组(栈区):若需修改字符串内容,且不用考虑内存分配问题,优选以字符数组的形式来处理字符串(适用于占用字节数比较少的字符串)
      • char 型数组 来表示一串字符,用格式字符 %s 使其以字符串的形式打印出来,eg:printf("The str is: %s\n ")
      • 字符数组的初始化:
        • char str[6] = { 'h', 'e', 'l', 'l', 'o','\0' } = { 'h', 'e', 'l', 'l', 'o', 0}; ,如果一串字符没有以'\0'(字符串结束符) 结束,则 printf 或 cout 会一直尝试打印下去;直到遇到'\0' 结束打印,'\0' 之后的字符不会被打印出来
        • char str[6] = "hello"; 编译器自动添加字符串结束符 '\0' ,字符串的长度为 5(strlen(str)),所以定义的字符数组容量必须大于等于 6(sizeof(str)
        • Note:字符串处理以'\0'(数字0)作为结束符,若后面还有字符,则不会输出
    • 动态字符串(堆区):
      • 可以动态地分配一块内存,然后在这块内存里存放一串字符
      • char* str = (char*) malloc(12); str[0] = 'h'; str[1] = 'i'; ......
    • 字符串常量(常量区):
      • 字符串常量是用双引号括起来的字符序列, 系统在字符串常量最后自动加字符串结束符号'\0' (ASCII 码编码值为 0 的字符,它是一个空字符 str[i] = 0; 等价于 str[i] = '\0';”)
      • 字符串常量的类型为: char* a = "hi"; // 省略了字符数组的定义 注意:C 中没有 char a = "hi"; 这种写法,因为 C 中没有专门的字符串变量
      • 字符串常量"hi"(实质是字符串的首地址) 是 char* 类型的, a 这个指针指向的内存是只读的,存在常量数据区
#include<stdio.h>
#include<stdlib.h>

int main() {
    // 1、栈区中存放的字符串可以被修改
    char stack_arr[] = "hello world";
    stack_arr[0] = 'm';
    stack_arr[1] = 'a';
    stack_arr[2] = 'n';

    // 2、堆区中存放的字符串可以被修改
    char *heap_arr = (char *) calloc(1, 12);
    *heap_arr = 'm';
    *(heap_arr + 1) = 'a';
    heap_arr[2] = 'n';

    // 3、常量区存放的字符串不能被修改,可以读取
    char *const_arr = "hello world";
    // *const_arr = 'm';  // error

    printf("stack arr is: %s\n", stack_arr);  // stack arr is: manlo world
    printf("heap arr is: %s\n", heap_arr);    // heap arr is: man
    printf("const arr is: %s\n", const_arr);  // const arr is: hello world

    if (heap_arr) {
        free(heap_arr);
        heap_arr = NULL;
    }
    return 0;
}
  • 字符串的操作:#include <string.h>
    • 字符串的长度:strlen("str") = 3 ,字符串的长度不包括末尾的'\0'sizeof("str") = 4,使用 sizeof 运算符得到的长度包含末尾的'\0'
    • 字符串的复制:strcpy(dst, src); ,只需传入字符串的首地址即可
    • 字符串的比较:int flag = strcmp(a, b); a > b, flag = 1; a = b, flag = 0; a < b, flag = -1
    • 其它字符串处理函数:gets()/fgets()/puts()/fpus()/strncpy()/strcat()/strncat()/strncmp()/strchr()/strstr()/strtok()/atoi()/atof()
    • 字符串的遍历、插入、删除、分割
#include <stdio.h>
#include <string.h>

// 1、字符串的遍历、复制及长度统计
int get_str_length(const char *str, char *dst) {
    int count = 0;
    while (str[count])              // 以字符串的结束符 0 作为判断条件
    {
        printf("%c ", str[count]);  // 字符串遍历
        dst[count] = str[count];    // 字符串复制
        count++;                    // 字符串长度统计
    }

    dst[count] = 0;
    printf("\nThe copied str is: %s \n", dst);
    printf("\nThe length of this str is: %d \n", count);
    return 0;
}

// 2、删除某个字符,只需将其后面的字符依次前移即可
char del(char *str, int index) {
    int len = strlen(str);
    char ch = str[index];
    for (int i = index; i < len; i++) {
        str[i] = str[i + 1];      // 后面的字符前移
    }

    return ch;                    // 返回删除的字符
}

// 3、插入某个字符,从 index 位置开始的字符(包括)依次往后移动,最高位先开始移动,然后空出位置给次高位移动,以此类推
void insert(char *str, int index, char ins) {
    int len = strlen(str);
    for (int i = len; i >= index; i--) {
        str[i + 1] = str[i];
    }
    str[index] = ins;
}

int main() {
    char src[7] = "hello";
    insert(src, 3, 'w');
    printf("插入的字符'w'后的字符串为:%s \n", src);
    return 0;
}


// 4、字符(串)标准输入输出函数
char getchar(void);       // char ch = getchar();
int putchar(int c);       // putchar(ch);

char* gets_s(char* s);    // 连续读取一连串的字符,包括标点符号和空格,遇到'\n'时结束读取
                          // scanf_s 用于获取单词而不是字符串,所以它在遇到空格时会结束读取
int puts(const char* s);  // 只能配合 gets_s 使用,需指定一个 char* 类型的常指针, eg: puts(buf);
                          // printf 标准格式化输出,功能强大,但不如 puts 效率高
#include<stdio.h>
#include<string.h>

int main() {
    char buf[1024] = {0};

    char str1[] = "hello";
    char str2[] = "world";
    int len = sprintf(buf, "%s %s", str1, str2);
    printf("buf is: %s, str len is: %d\n", buf, len);  // buf is: hello world, str len is: 11

    memset(buf, 0, 1024);
    sscanf("123456aaaa", "%*d%s", buf);  // 跳过前面的数字,输出 buf:aaaa
    printf("buf:%s\n", buf);

    memset(buf, 0, 1024);
    sscanf("123456aaaa", "%7s", buf);   // 读取指定宽度的数据,输出 buf:123456a
    printf("buf:%s\n", buf);

    return 0;
}

  • 拼接字符串,拷贝字符串,格式化字符串 sprintf
  • strncpy,memcpy,memmove 的区别及工业中用哪个?

2、C++ 中的字符串

  • 不同于字符数组(C 风格字符串实现),C++ 中提供了 std::string,可动态调整其长度,使用更加灵活可靠
  • eg:string greetString ("Hello std::string!"); ,通过 .length() 方法确定其长度
  • Note: 计算字符串的长度时,不包含末尾的字符串结束符(\0

五、静态数组与动态数组

1、静态数组

  • 长度在编译阶段就已确定
  • 不能存储更多的数据,而且即使有部分元素未被使用,它们占据的内存也不会减少
  • C++提供了 std::(c)begin, std::(c)end(其中 c 为可选项,表示 const)获得指向数组开头与结尾的指针

2、动态数组

  • 长度在执行阶段确定,可动态地调整其长度
  • 无需在编译阶段考虑其最大长度,可更好地管理内存,以免分配过多的内存,而又不使用它们
  • C++提供了 std::vector 来使用的动态数组,eg:vector<int> dynArray (3); // dynamic array of int

3、动态内存分配

  • C++ 中的 newdelete 分别用来分配和释放自由存储区中的内存,自由存储区是一种内存抽象,表现为一个内存池,应用程序可分配(预留)和释放其中的内存,它们与 C 语言中 malloc()、free() 最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。构造函数和析构函数对于来说是不可或缺的,所以在 C++ 中推荐使用 newdelete

  • C++ 中,使用 new 运算符创建一个结构体变量时,默认情况下,结构体的成员不会被初始化;new运算符只会分配内存空间,并返回指向该空间的指针

    • 方法1:在结构体定义的时候定义构造函数;使用的时候直接传参进行初始化;
    • 方法2:在结构体定义的时候使用默认初始化;
    • 方法3:对结构体中的成员变量进行赋值(或先 memset 0;后面用的时候再赋值)
  • 为单个元素分配内存:

    Type* Pointer = new Type{};     // request memory for one element and init 
    delete Pointer;                 // release memory allocated above
    
    // 类动态内存分配并初始化
    SomeClass* PointerToClass = new SomeClass{"test", 123};     
    delete PointerToClass ;         
    
    // eg: new 请求分配内存并不能保证总能得到满足,因为这取决于系统的状态 以及内存资源的可用性
    int* pointToAnInt = new int{123};  // get a pointer to an integer and init with 123
    delete pointToAnInt;
    
  • 为多个元素分配内存:

    Type* Pointer = new Type[numElements]; // request memory for numElements
    delete[] Pointer;                      // release block allocated above
    
    // eg: new 请求分配内存并不能保证总能得到满足,因为这取决于系统的状态 以及内存资源的可用性
    int* pointToNums = new int[10];        // get a pointer to an integer
    delete[] pointToNums;
    
    // 根据用户的要求动态地分配内存块,以便能够存储指定数量的 int 变量(由用户输入)
    int numEntries = 0;
    cin >> numEntries;
    int* myNumbers = new int[numEntries];
    
    cout << "Allocated for " << numEntries << " integers" << endl;
    for(int counter = 0; counter < numEntries; ++counter) 
    {
    	cout << "Enter number "<< counter << ": "; 
    	cin >> *(pointsToInts + counter);
    }
    
    cout << "Displaying all numbers entered: " << endl;
    for(int counter = 0; counter < numEntries; ++counter)
    {
    	cout << *(pointsToInts++) << " ";  // pointsToInts 所指向的地址改变了
    }
    cout << *(pointsToInts++) << " ";
    
    // return pointer to initial position
    // 调用 delete[]来释放内存时,必须是分配内存时 new 返回的指针地址
    pointsToInts -= numEntries;
    delete[] myNumbers;
    
  • 内存泄露:

    • 使用 new 动态分配的内存不再需要后,没有使用配套的 delete 释放
    • 程序占用的内存没有及时释放,导致系统可用内存量的减少
    • 如果有指针的多个拷贝,只需对其中一个调用 delete
    • C++ 没有 自动垃圾回收机制 对程序已分配但不能使用的内存进行清理,所以使用指针来管理内存资源时要特别当心
      int* pointToNums = new int[5];  // initial allocation 
      
      // use pointToNums 
      
      ...
      // forget to release using delete[] pointToNums; 
      ...
      
      // make another allocation and overwrite 
      pointToNums = new int[10];  // leaks the previously allocated memory
      

五、指针与引用

1、指针

  • 为什么要引入指针?

    • 指针可以有效的表示复杂的数据结构,方便的使用数组和字符串,不再需要进行参数拷贝
    • 在调用函数时可以获得一个以上的结果(通过结构体或类)
    • 能动态分配内存并直接处理内存单元,效率较高(函数参数传递,只复制了地址,相比大型结构体复制成本低,但读写成本稍高)
    • 指针
    • Note: 指针也是一种数据类型,指针变量是一种变量
  • 指针变量定义:
    在这里插入图片描述

    • 是一种指向内存单元的 特殊变量,它的内容永远是内存地址
    • 占用几个字节的内存跟系统的寻址能力有关,一般 32 位系统占用 4 个字节内存,64 位系统占用 8 个字节内存,不会随着指向数据的数据类型而变
    • 声明:数据类型* 标识符;
      • * 后面的标识符指针变量名(用于存放另一变量的地址)* 本身不是变量名的组成部分,只是用来声明后面的标识符是一个指针类型的变量
      • 指针定义中的数据类型不是指针本身的数据类型,而是指针所指的对象的数据类型,指针变量中地址数据占用的内存不会随着指向数据的数据类型而变
      #include <iostream>
      using namespace std;
      
      int main()
      {
         cout << "sizeof fundamental types:" << endl;
         cout << "sizeof(char) = " << sizeof(char) << endl;
         cout << "sizeof(int) = " << sizeof(int) << endl;
         cout << "sizeof(double) = " << sizeof(double) << endl;
      
         cout << "sizeof pointers to fundamental types:" << endl;
         cout << "sizeof(char*) = " << sizeof(char*) << endl;
         cout << "sizeof(int*) = " << sizeof(int*) << endl;
         cout << "sizeof(double*) = " << sizeof(double*) << endl;
      
         return 0;
      }
      
      
      sizeof fundamental types:
      sizeof(char) = 1 
      sizeof(int) = 4 
      sizeof(double) = 8
      
      // 将 sizeof( ) 用于指针时,结果取决于编译程序时使用的编译器和针对的操作系统,与指针指向的变量类型无关
      // 存储指针所需的内存量相同
      sizeof pointers to fundamental types: 
      sizeof(char*) = 4 
      sizeof(int*) = 4 
      sizeof(double*) = 4
      
  • 指针初始化:在定义的同时赋初值

    • 地址运算符(引用运算符) &&变量,取得变量在内存中的地址,一般变量第一个字节的地址就是变量的地址
    • 读/写内容运算符 **p 用于访问 p 指向的内存(读、写)注意,p 为地址
      这里写图片描述
  • 指针运算:

    • 赋值运算:

      int *p, a;
      p = NULL;          // 指针的值为 NULL(0), 表示不指向任何对象
      p = &a;              // p 指向变量 a
      
    • 指针步长与指针类型:

      • 指针步长:指针变量+1,要向后跳多少字节
      • 指针类型:不仅决定指针的步长,还决定解引用*的时候从给定地址开始取类型大小的字节数
       char buf[1024] = {0};
       int a = 100;
       memcpy(buf + 1, &a, sizeof(int));
      
      // 不同数据类型的变量赋值后,解引用需要进行数据类型的强转
       printf("*(int *)(buf+1) = %d\n", *(int *) (buf + 1));   // 输出 *(int *)(buf+1) = 100
      
    • p++p--p+np-n 运算(p 为指针,n 为整数):

      • 自增或自减后 p 指向后一个或前一个元素,指针的值变为后一个变量或前一个变量的地址
      • p 的值+n*sizeof(p 指向的类型),p 本身的值并没有变
        int a = 5;  // 假设 a 的地址为 0x0045FE00, 则 a 所占用的内存块为:0x0045FE00~0x0045FE04 这四个字节(一个地址对应一个字节的数据)
        int* p = &a;  
        // p 指向 a,则 p 的值为 0x0045FE00, p + 2 的值为:0x0045FE00 + 2*sizeof(int) =  0x0045FE00 + 2*4 =  0x0045FE08
        // 对于int* 来说,元素大小是 4 字节,所以后移两个元素、自然地址要加 2*4
        
    • 指针相减:两个类型相同的指针可以相减,结果为这两个地址差之间能够存放数据的个数(数据类型为指针所指的类型)

      int *p1, *p2;
      // 假设 p1 指向 1000,p2 指向 1008,则 p2-p1 的值为:(1008 - 1000) / sizeof(int) = 2
      
  • 将关键字 const 用于指针:

    • 指针指向的 数据为常量,不能修改,但可以修改指针包含的地址,即指针可以指向其他地方

      int hoursInDay = 24;
      const int* pointsToInt = &hoursInDay;  // * 在 const 后面,数据(*pointsToInt )是常量,只读 
      int monthsInYear = 12;
      pointsToInt = &monthsInYear;  // OK!
      
      *pointsToInt = 13;   // Not OK! Cannot change data being pointed to 
      int* newPointer = pointsToInt;   // Not OK! Cannot assign const to non-const
      
    • 指针包含的 地址是常量,不能修改,但可修改指针指向的数据

      int daysInMonth = 30;
      int* const pDaysInMonth = &daysInMonth;  // * 在 const 前面,地址(pDaysInMonth)是常量
      *pDaysInMonth = 31;   // OK! Data pointed to can be changed 
      
      int daysInLunarMonth = 28;
      pDaysInMonth = &daysInLunarMonth;   // Not OK! Cannot change address!
      
    • 指针包含的地址以及它指向的都是常量,不能修改

      int hoursInDay = 24;
      const int* const pHoursInDay = &hoursInDay;
      *pHoursInDay = 25;   // Not OK! Cannot change data being pointed to 
      
      int daysInMonth = 30;
      pHoursInDay = &daysInMonth;   // Not OK! Cannot change address
      
  • 多级指针:

    • 二级指针:指向一个一级指针变量地址的指针
    • 三级指针:指向一个二级指针变量地址的指针
      在这里插入图片描述
#include <stdio.h>

int main() {
    int a = 10;

    // 一级指针变量:指向普通变量
    int* p = &a;  // *p 就是 a,*p = 10

    // 二级指针变量:指向一级指针变量
    int** q = &p; // *q 就是 p,**q 就是 a

    // 三级指针变量:指向二级指针变量
    int*** t = &q; // *t 就是 q,**t 就是 p,***t 就是 a

    printf("p is %p, *p is %d!\n", p, *p);
    printf("q is %p. *q is %p, **q is %d!\n", q, *q, **q);
    printf("t is %p. *t is %p, **t is %p, ***t is %d!\n", t, *t, **t, ***t);
    return 0;
}

>>>
p is 0x7ffc591774fc, *p is 10!
q is 0x7ffc59177500. *q is 0x7ffc591774fc, **q is 10!
t is 0x7ffc59177508. *t is 0x7ffc59177500, **t is 0x7ffc591774fc, ***t is 10!
  • 安全的使用指针:
    • 杜绝野指针:
      • 一个指针未赋值时,其值为随机值,此时指向了一个随机的内存地址,称为野指针
      • 野指针不会直接引发错误,操作野指针指向的内存区域才会出问题(假如指向了系统启动区,然后又操作了此内存区域,就可能出现问题)
      • 函数内无法判断野指针,所以指针变量在创建的同时应当被初始化,要么将指针设置为 NULL,要么让它指向合法的内存
      • Note:
        • 指针在 free 或 delete 后(只是把指针所指的内存给释放掉,并没有把指针本身干掉)应立即置空(free(p); p=NULL;)
        • 不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放
    • 指针只允许指向两个地方:指向变量、数组或者指向 NULL(0)
      • 使用指针前判断其指向的变量是否已经失效
      • 如果指向的目标的生命期已经终结(失效),则该指针也失效
    • 提前判断空指针:
      • int* p = NULL; 指针变量的值为 0 的指针,称为空指针,此时不能使用星号(*p)进行解引用取值操作,但可以使用p对其进行赋值操作(指向新的地址)
      • int** p = NULL; 指针变量的值为 0 的指针,称为空指针,此时不能使用星号(**p 或 *p)进行解引用取值操作,但可以使用(*p 或 p)进行赋值
      • 传递空指针:函数内部是有办法处理,判断一下就行if (p)
    • 不同类型的指针不能互相赋值

2、引用

a、引用基本语法
  • 引用的定义:Type& Ref = Variable& 写到变量左侧叫引用,写到右侧叫取地址
    • 引用就是为已定义变量起个别名
    • 变量名实质上是一段连续内存空间的标号;程序中通过变量来申请并命名内存空间,通过变量的名字即可使用内存空间
  • 引用必须初始化,且初始化后不能修改指向(值可以改变);必须确保引用的是一块合法的内存(不能是 NULL
int a = 10;
int A = 20;

int& b = a;  // 引用必须初始化,给变量 a 取一个别名 b,与 a 共用一段内存
// int& b = A;  // 报错,初始化后不可修改指向。但可以操作 b 或 a 可以改变其值
int c = a;   // 把 a 的值赋给 c,c 会另外开辟一块内存

b = 100;     // 操作 b 就相当于操作 a 本身(指向同一块内存),此时 a,b 均为 100

// 数组中的引用
int arr[10] = {0};
int(&f)[10] = arr;  //  注意,需要加 [10]
for (int i = 0; i < 10; i++){
	f[i] = i + 10;
}
  • 函数中的引用
// 值传递
void ValueSwap(int m, int n) {
    int tmp = m;
    m = n;
    n = tmp;
}

// 地址传递
void PointerSwap(int *m, int *n) {
    int tmp = *m;
    *m = *n;
    *n = tmp;
}

// 引用传递
void ReferenceSwap(int &m, int &n) {
    int tmp = m;
    m = n;
    n = tmp;
}

void test() {
    int a = 10;
    int b = 20;

    ValueSwap(a, b);  
    // 值传递,不会改变函数外参数的值,此时 a=10, b=20
    cout << "a:" << a << " b:" << b << endl;  //

    PointerSwap(&a, &b);  
    // 地址传递,地址不会变,但会改变函数外参数的值,此时 a=20, b=10
    cout << "a:" << a << " b:" << b << endl;

    ReferenceSwap(a, b);  
    // 引用传递,地址不会变(type & var= type* const var)
    // 会改变函数外参数的值,此时 a=10, b=20
    cout << "a:" << a << " b:" << b << endl;
}
  • 局部变量的引用和静态变量的引用
// 返回局部变量引用
int &TestFun01() {
    int a = 10;
    return a; // 不要返回局部变量的引用
}

// 返回静态变量引用
int &TestFunc02() {
    static int a = 20;
    cout << "static int a : " << a << endl;
    return a;  // 可以返回静态变量的引用
} // 如果函数的返回值是静态变量的引用,那么这个函数调用可以作为左值
b、引用的本质
  • 引用的本质: Type& ref = val; =========> Type* const ref = &val;,本质是一个常量指针
  • C++ 编译器在编译过程中使用常量指针对其进行替换,此过程对用户不可见
  • 所以引用不可以改变指向,但可以改变内存中的值,占用内存大小与指针相同
// 发现是引用,转换为 int* const ref
void testFunc(int &ref) {
    ref = 100; // ref 是引用,转换为 *ref = 100
}

int main() {
    int a = 10;
    int &aRef = a; // 自动转换为 int* const aRef = &a
    aRef = 20;     // 内部发现 aRef 是引用,自动帮我们转换为: *aRef = 20;

    cout << "a:" << a << endl;  // 20
    cout << "aRef:" << aRef << endl;  // 20

    testFunc(a);
    cout << "a:" << a << endl;  // 100
    return 0;
}
c、指针引用和常量引用
  • 指针引用:用一级指针引用可以代替二级指针
  • 常量引用:可以修饰形参为只读,主要用在函数的形参,尤其是类的拷贝/复制构造函数
  • 将函数的形参定义为常量引用的好处:
    • 引用不产生新的变量,减少形参与实参传递时的开销
    • 如果希望实参随着形参的改变而改变,那么使用一般引用,如果不希望实参随着形参改变,那么使用常引用
// 常量引用:防止函数中意外修改数据
void ShowVal(const int& param){
	cout << "param:" << param << endl;
}

3、指针与引用的区别

  • 指针:
    • 可以改变其指向
    • 可以为空指针
    • 地址信息可能非法
    • 与引用不同,指针上的算术和关系操作所指的是机器地址,它们是指针本身的值,而不是指向变量的值
  • 引用:
    • 构造时绑定对象,在其生命周期内不能绑定其它对象,即不能改变其指向(和指针的区别);这也导致其不能像指针那样以递增的方式遍历数组
    • 不存在空引用,但可能存在非法引用,总的来说比指针安全
    • 属于编译期概念,在底层还是通过指针实现;Type& ref = val; =========> Type* const ref = &val;,本质是一个常量指针
    • 当引用作为函数参数时,如果在函数体内部不会修改引用所绑定的数据,那么请尽量为该引用添加 const 限制;给引用添加 const 限定后,不但可以将引用绑定到临时数据,还可以将引用绑定到类型相近的数据,即可进行数据类型的自动转换

六、指针和数组

  • 获得指向数组开头与结尾的指针 : std::(c)begin(ptr), std::(c)end(ptr),其中 c 为可选参数,为 const 的缩写
  • 指针和一维数组
    • 数组指针:一个指针指向了数组首地址,int *p = arr;p 的类型是 int *,arr 中每个元素都是 int
    • 指针数组:一个数组中的所有元素保存的都是指针,int *p = arr[5];p 的类型是 int *[5],arr 中每个元素都是 int*
int a[3] = {10, 20, 30};
int *p;

// 当执行 p = a 或者 p = &a[0] 时,有如下等价关系成立:
p = a = &a[0]  // 数组首地址                    
p + i = a + i = &a[i]  // 数组第 i 个元素的地址
*(p + i) = *(a + i) = a[i] = p[i]  // 可以把 p 当成数组来用 

// p 与 a 的区别:
// 两者同样是表示数组 a 的地址,在表现形式上可以互换,但它们本质不同,p 是地址变量,而 a 是地址常量,不可改变

int myNumbers[100];   // myNumbers 是一个 int 数组,它指向这样的内存单元的开头,即其中存储了 100 个整数

// 每个元素都是指针类型
int* myArrays[100];   // myArrays 是一个包含 100 个元素的指针数组,其中的每个指针都可指向 int 或 int 数组
  • 指针和字符串
char s[50] = "hello world";  // 字符串,编译器自动添加字符串结束符 '\0',数据类型为 char [50]
char *p = s;   // 亦可直接执行 char* p = "hello world", p 就直接指向了字符串的首字符

// 当执行 p = s 或者 p = &s[0] 时,有如下等价关系成立:
p = s = &s[0];
p + i = s + i = &s[i];
*(p + i) = *(s + i) = s[i] = p[i];   // 可以把 p 当成数组来用,*p = 'h'(首个字符的值)


*p = 'm';    // 将字符数组的首个字符替换为 'm',此时指针变量指向字符数组的第一个元素 'm'
p++;         // 此时指针变量指向字符数组的第二个元素
*p = 'i';    // 将字符数组的第二个字符替换为 'i',此时指针变量指向字符数组的第二个元素 'i'

printf("%s\n", s);  // 输出 millo world,只要给定数组首地址,然后以字符串形式打印即可输出完整字符数据
printf("%s\n", p);  // 输出 illo world,指针变量指向字符数组的第二个元素 'i'

p = "like han";     // 指向字符数组的指针变量重新赋值,指向此字符串的首地址(省略了字符数组的定义)
printf("%s\n", p);  // 此时输出 like han


// p 与 s 的区别:
// 两者同样是表示数组 s 的地址,在表现形式上可以互换,但它们本质不同,p 是地址变量(可以改变指向),而 s 是地址常量(不可改变指向)
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值