分享

C语言之指针与数组

 山峰云绕 2023-03-14 发布于贵州

https://m.toutiao.com/is/SbU6MJ8/ 

一维数组中的指针

数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示:

一维数组名:可以隐式转换为指向数组首地址的指针

定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:

数组下标为啥从0开始?

数组下标实际上是每个元素的地址相对于第一个元素地址的偏移量。如下图所示:

一维数组名可以隐式转换为指向数组首地址的指针,但是不可对数组名直接自增自减操作,数组名的位置不可改变。所以一般会用指针指向数组去操作,如下测试代码:

#include <stdio.h>int main() { int arr[5] = { 99, 15, 100, 888, 252 }; int* p = arr; //等效 p=&arr[0] //错误,数组名不可改变位置 //arr++; for (int i = 0; i < 5; i++) { //*(arr + i)等效arr[i] 等效p[i]等效(arr+i)[0]等效(p+i)[0] printf('%d\t', *(arr + i)); } printf('\n'); return 0;}

&一维数组名:可以隐式转换为指向整个数组的数组指针

数组指针是指向整个数组的指针,在做偏移的时候就是整个数组占用的字节数。如下测试代码:

#include <stdio.h>int main() {  int arr[5] = { 99, 15, 100, 888, 252 };  int(*p)[5] = &arr;  printf('p:%p\t&arr:%p\n', p, arr);  printf('p+1:%p\t&arr+1:%p\n', p + 1, arr + 1);  for (int i = 0; i < 5; i++)  {    printf('%d\t', p[0][i]);  }  printf('\n');  return 0;}

运行结果如下:

0000001512BEFB5C- 0000001512BEFB48=0000000000000014 ,十六进制0000000000000014 就是十进制20,刚好是sizeof(int)*5 个字节,自己电脑上运行的地址肯定会改变,但是偏移量肯定是一样的。

数组名和普通指针的区别

虽然说数组名可以当做指针使用,但实际上数组名并不等价于指针。

  • 数组名代表的是整个数组,具有确定数量的元素
  • 指针是一个标量,不能确定指向的是否是一个数组
  • 编译器会把数组名转换为一个指针常量,是数组中的第一个元素的地址

如下测试代码:

#include <stdio.h>int main() { int arr[5] = { 99, 15, 100, 888, 252 }; //隐式转换 int* p = arr; //指针变量64位中占用8个字节 printf('sizeof(p):%zd\n', sizeof(p)); //整个数组占用内存 printf('sizeof(arr):%zd\n', sizeof(arr)); //数组名取地址并不是二级指针,而是数组指针 int(*parr)[5] = &arr; for (int i = 0; i < 5; i++) { printf('%d\t', parr[0][i]); } printf('\n'); return 0;}

运行结果如下:

指针操作一维数组

示例程序| 各种方式遍历一维数组

在一维数组中以下访问元素的方式都是等效的,不过在用指针操作的时候强烈建议新手采用下标法。

  • *(arr+i)
  • arr[0]
  • (arr+i)[0]

当指针指向一维数组的首地址的时候,也存在上述一样的用法,并且还有其他的使用方式,如下测试代码:

#include <stdio.h>int main() {  int arr[5] = { 99, 15, 100, 888, 252 };  //隐式转换  printf('数组名指针方式:\n');  for (int i = 0; i < 5; i++)  {    //*(arr+i)等效arr[i]    printf('%d\t',*(arr+i));  }  int* p = arr;  printf('\n指针方式:\n');  for (int i = 0; i < 5; i++)  {    printf('%d\t', *(p + i));  }  printf('\n指针下标方式1:\n');  for (int i = 0; i < 5; i++)  {    printf('%d\t', p[i]);  }  printf('\n指针下标方式2:\n');  for (int i = 0; i < 5; i++)  {    printf('%d\t', (p+i)[0]);  }  printf('\n指针偏移方式:\n');  for (int i = 0; i < 5; i++)  {    //等效*(p++);    printf('%d\t',(p++)[0]);  }  printf('\n数组指针方式:\n');  int(*parr)[5] = &arr;  for (int i = 0; i < 5; i++)   {    printf('%d\t', parr[0][i]);  }  printf('\n');  return 0;}

运行结果如下:

示例程序| 指针操作一维数组负下标理解

在一维数组中如果用指针指向数组首地址后,在操作过程中对指针对了一些偏移后,或者指针不是指向首地址,下标法的含义是完全不同的,如下测试代码:

#include <stdio.h>int main() { int arr[5] = { 99, 15, 100, 888, 252 }; int* p = &arr[2]; //p[0]等效 *(p+0) printf('%d\n', p[0]); //p[-2]等效*(p-2); printf('%d\n', p[-2]); return 0;}

运行结果与图解如下:

示例程序| 指针操作字符串(字符数组)

指针操作字符类数组,要注意的是边界问题,尤其是在字符串中的字符调整,例如反转字符串操作,统计字符串长度等操作。指针操作字符串,注意常量字符串,建议养成const修饰指针的写法,C++对于const要求更为严格,如下测试代码:

#include <stdio.h>int main() {  //表示字符串常量,字符串常量的首地址赋值给指针了  //建议加上const修饰 C++中没有const是错误的  const char* cpstr = 'coolmoying';  //错误常量无法被修改  //cpstr[0] = 'A';  puts(cpstr);  //指针单纯存储地址功能,可更改存储内容  cpstr = 'Iloveyou';  puts(cpstr);  //把'coolmoying'拷贝到了str中  char str[] = { 'coolmoying' };  char* pstr = str;  //可修改变量的数据  pstr[0] = 'A';  puts(pstr);  //统计字符串可见长度  int count = 0;  while (*pstr++ != '\0')    count++;  printf('length:%d\n', count);  return 0;}

运行结果如下:

二维数组中的指针

二维数组可以理解为每一个元素都是一个一维数组的数组,这样就可以很好的理解二维数组与指针了。下面定义了一个2行3列的二维数组,内存模型如下:

二维数组名:可以隐式转换指向数组的指针

数组指针在做偏移的时候,每次都是偏移一行,如下测试代码:

#include<stdio.h>int main(){ int arr[2][3] = { 1,2,3,4,5,6 }; int(*p)[3] = arr; printf('%p\t%p\n', p + 1, &arr[1][0]); for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf('%d\t', p[i][j]); } printf('\n'); } return 0;}

运行结果如下:

二维数组名[下标]:可以隐式转换一级指针

二维数组名[i]:代表第i行的首地址,可以隐式转换为一级指针。

&二维数组名[i] :代表的是第i行的首地址,并且可以隐式转换为数组指针。

&二维数组名[i][j]:当前行列元素的地址,

如下测试代码:

int main(){    int arr[2][3] = { 1,2,3,4,5,6 };    for (int i = 0; i < 2; i++)    {        int* p = arr[i];        for (int j = 0; j < 3; j++)        {            printf('%d\t', p[j]);        }        printf('\n');    }    int(*pp)[3] = &arr[0];    for (int i = 0; i < 2; i++)     {        for (int j = 0; j < 3; j++)         {            printf('%d\t', pp[i][j]);        }        printf('\n');    }    return 0;}

运行结果如下:

行列指针

行指针:是指向一整行,并不是指某个具体元素

  • 二维数组名[下标]

列指针:是指向某个具体元素

  • 二维数组名
  • 二维数组名+下标

指针操作二维数组

示例程序| 一级指针遍历二维数组

在显存指针操作的时候就需要使用一级指针操作二维数组,由于二维数组的内存是连续的,所以一级指针依然可以操作,只需要把行列转换为序号即可,如下测试代码:

#include<stdio.h>int main(){ int arr[2][3] = { 1,2,3,4,5,6 }; int* p = &arr[0][0]; for (int i = 0; i < 2; i++) { for (int j = 0; j < 3; j++) { printf('%d\t', p[i*3+j]); } printf('\n'); } return 0;}

运行结果如下:

示例程序| 数组指针遍历二维数组

二维数组访问元素的方式比较多,以下方式都可以:

  • arr[i][j]
  • *(*(arr+i)+j)
  • *(arr[i]+j)
  • *((arr+i)[0]+j)
  • (arr[i]+j)[0]
  • ((arr+i)[0]+j)[0]

当数组指针指向一维数组的首地址的时候,也存在上述一样的用法,并且还有其他的使用方式,如下测试代码:

#include<stdio.h>int main(){    int arr[2][3] = { 1,2,3,4,5,6 };    int (*p)[3] = arr;    for (int i = 0; i < 2; i++)    {        for (int j = 0; j < 3; j++)        {            printf('%d\t', p[i][j]);        }        printf('\n');    }    for (int i = 0; i < 2; i++)    {        for (int j = 0; j < 3; j++)        {            printf('%d\t',*(*(p+i)+j));        }        printf('\n');    }    for (int i = 0; i < 2; i++)    {        for (int j = 0; j < 3; j++)        {            printf('%d\t', *(p[i] + j));        }        printf('\n');    }    for (int i = 0; i < 2; i++)    {        for (int j = 0; j < 3; j++)        {            printf('%d\t', (p[i] + j)[0]);        }        printf('\n');    }    for (int i = 0; i < 2; i++)    {        for (int j = 0; j < 3; j++)        {            printf('%d\t', *((p + i)[0] + j));        }        printf('\n');    }    for (int i = 0; i < 2; i++)    {        for (int j = 0; j < 3; j++)        {            printf('%d\t', ((p+i)[0] + j)[0]);        }        printf('\n');    }    return 0;}

示例程序| 数组指针操作多个字符串

多个字符串可以采用二维数组存储,每一行存储一个字符串,操作每一行即可操作每个字符串,例如多个字符串的排序问题,如下测试代码:

#include<stdio.h>#include<string.h>int main(){ char str[][10] = { 'coolmoying','moying','ying' }; char (*p)[10] = str; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3 - i - 1; j++) { if (strcmp(p[j], p[j+1])>0) { char temp[10] = ''; strcpy(temp, p[j]); strcpy(p[j], p[j + 1]); strcpy(p[j + 1], temp); } } } for (int i = 0; i < 3; i++) { puts(p[i]); } return 0;}

动态内存申请

C语言内存四区

C语言在程序执行的时候,内存划分为4个区域

  • 代码区:存放函数体的二进制代码,由操作系统进行管理的。
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等。
  • 堆区:由程序员分配和释放,若程序员不释放,程序结束后由操作系统回收。

C语言内存申请和释放函数

C语言使用内存申请和释放函数一定要记得包含stdlib.h头文件。不包含可能会导致申请内存失败

  • malloc函数:void* malloc(size_t _Size);_Size:申请内存字节数返回申请内存的首地址申请内存不会做初始化
  • calloc函数:void* calloc(size_t _Count,size_t _Size);_Count:申请几个_Size:每个占用字节数申请内存并且默认初始化为0
  • realloc函数:void* realloc(void* _Block,size_t _Size);_Blcok: 重新申请内存的首地址,含原数据_Size:重新申请内存大小,要大于原大小申请失败返回空, 申请成功返回申请内存首地址
  • free函数:void free(void* _Block);_Block:释放的内存首地址同一段内存不能被重复释放哪里申请的内存就要从哪里释放,指针做了偏移一定要还原释放内存后,指针要置空,防止悬浮指针存在

动态申请内存基本操作

示例程序| 一级指针申请单个变量内存

动态申请内存过程其实是分为两个过程

  • 在堆区创建变量
  • 把变量首地址赋值给指针

如下测试代码:

#include<stdio.h>#include<assert.h>#include<stdlib.h>int main(){    int* pInt = (int*)malloc(sizeof(int));    //申请内存失败判定    assert(pInt);    *pInt = 123;    char* pChar = (char*)malloc(sizeof(char));    //申请内存失败判定    if (pChar == NULL)        return 0;    putchar(*pChar = 'A');    free(pInt);    pInt = NULL;    free(pChar);    pChar = NULL;    return 0;}

示例程序| 一维数组动态申请内存

一维数组动态内存申请其实就是申请一段内存,然后把一段内存的首地址赋值给指针,通过指针偏移操作多个内存。不过一般操作方便,推荐使用数组下标方式访问,一维数组动态主要有一下三种方式:

  • 直接申请一段内存
  • 通过传参方式,子函数修改指针指向,需要传入二级指针
  • 通过返回值方式

测试代码如下:

#include<stdio.h>#include<assert.h>#include<stdlib.h>int main(){ int* pInt = (int*)malloc(sizeof(int)); //申请内存失败判定 assert(pInt); *pInt = 123; char* pChar = (char*)malloc(sizeof(char)); //申请内存失败判定 if (pChar == NULL) return 0; putchar(*pChar = 'A'); free(pInt); pInt = NULL; free(pChar); pChar = NULL; return 0;}

运行结果如下:

示例程序| 一维数组动态申请之内存自动扩增

realloc函数可以实现内存的自动扩增,当用户数据超过限定的时候,可以做到自动增长,如下测试代码:

#include<stdio.h>#include<assert.h>#include<stdlib.h>int main(){    //申请一个内存大小    int* arr = (int*)malloc(sizeof(int));    assert(arr);    //最大存储数    int max = 1;    //当前元素个数    int curSize = 0;    int temp=0;    while (1)     {        //内存自动扩增        if (curSize >= max)        {            max *= 2;            int* p = realloc(arr, sizeof(int) * max);            assert(p);            arr = p;        }        int result=scanf('%d', &temp);        //输入0退出输入状态        if (temp == 0)            break;        arr[curSize++] = temp;    }    for (int i = 0; i < curSize; i++)     {        printf('%d\t', arr[i]);    }    free(arr);    arr=NULL;    return 0;}

运行结果如下:

示例程序| 二维组动动态申请内存

二维数组的动态内存申请主要有一下两种方式

  • 数组指针实现
  • 二级指针实现(如果用子函数实现需要传入三级指针)

测试代码如下:

#include<stdio.h>#include<assert.h>#include<stdlib.h>int main(){ int row = 3; int cols = 4; //No.1 数组指针申请 int(*p)[4] = NULL; p = (int(*)[4])calloc(row, sizeof(int[4])); assert(p); for (int i = 0; i < row; i++) { for (int j = 0; j < cols; j++) { printf('%d ', p[i][j]); } printf('\n'); } //No.2 二级指针申请 int** parr = (int**)calloc(row, sizeof(int*)); assert(parr); for (int i = 0; i < row; i++) { parr[i] = (int*)calloc(cols, sizeof(int)); assert(parr[i]); } for (int i = 0; i < row; i++) { for (int j = 0; j < cols; j++) { printf('%d ', parr[i][j]); } printf('\n'); } for (int i = 0; i < row; i++) { free(parr[i]); parr[i] = NULL; } free(parr); parr = NULL; free(p); p= NULL; return 0;}

示例程序| 字符指针申请内存并且赋值字符串

字符指针操作字符串的时候,主要是要注意字符串长度问题,一级字符串操作必须采用字符串处理函数去完成,如下测试代码:

#include<stdio.h>#include<assert.h>#include<stdlib.h>char* initStr(const char* initString) {    int length = strlen(initString);    //申请内存长度加1,要考虑字符串结束标记    char* pstr = (char*)malloc(length + 1);    //字符串赋值用strcpy函数    strcpy(pstr, initString);    return pstr;}int main(){    char* pstr = NULL;    pstr = initStr('coolmoying');    puts(pstr);    return 0;}

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多