欢迎光临散文网 会员登陆 & 注册

C语言数组和指针

2023-01-28 15:54 作者:虚云幻仙  | 我要投稿

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#define LEN 3


void pointer_array(void);

void copy_array(void);

void copy_array1(double src[], int len, double dest[]); // 以一维数组为形参时不需要指定数组长度,即使写了也无效,长度需要单独传参,形参列表为(原数组,数组长度,目标数组)

void copy_array2(double* src, int len, double* dest); // 数组实际上是通过指针实现的,所以形参声明数组和声明指针等价,所有数组形式的操作都会被编译器转换为指针形式的操作

void copy_array3(double* src_start, double* src_end_next, double dest[]); // 指定数组的开始地址和最后一个元素往后下一个地址,将这一段连续的地址的值赋值给目标数组(目标地址及往后的一段相同的长度)

//应模仿赋值表达式的形式利于记忆,dest=src将src赋值给dest,拷贝函数写为func(dest,size,src)将src的size个元素赋值给dest

void show_array(double ar[], int len);

void show_max1(void);

void show_max2(int ar[], int len);

int show_max_index1(void);

int show_max_index2(double ar[], int len);

void array2d(void);

void array2d_alter(double ar[][4], int len);

void array2d_show(double ar[][4], int len);

void sub_array(void);

void array_vla(void);


void pointer_array(void) {

     double ar1[LEN] = { 1.0,2.0,3.0 }; // 声明数组会在内存中创建一块连续的区域,ar1在内存中创建了一块连续的3*8字节的区域,该区域的第一个double地址上(第一个8字节)的值为1.0,之后依次为2.0,3.0,ar1除了3*8的区域外没有再创建区域来引用这个区域,C没有引用变量,也就是说ar1就是数组本身,数组本身就是这个3*8的区域,而每个元素就是这个区域内的每个8字节

     printf("&ar1[0] = %p\n", &ar1[0]); //ar1[0]为数组的首元素,&ar1[0]为首元素的地址

     printf("ar1 = %p\n", ar1); //数组名ar1实际是ar1[0]的地址,常量

     if (ar1 == &ar1[0]) // ar1地址常量和&ar1[0]地址常量 地址相等,

     {

         printf("sizeof ar1 = %zd\n", sizeof(ar1)); //ar1的大小为24,即数组的大小

        printf("sizeof ar1[0] = %zd\n", sizeof(ar1[0])); //ar1[0]的大小为8,即首元素(double类型变量)的大小

        printf("sizeof &ar1[0] = %zd\n", sizeof(&ar1[0])); //&ar1[0]的大小为8,是ari[0]double类型变量的地址常量的大小,本系统中存储地址的类型(指针类型)大小为8

         printf("ar1[0] = %.1f\n", ar1[0]); // ar1[0] = 1.0

         printf("*ar1 = %.1f\n", *ar1); // *ar1 = 1.0 ,使用*解引用数组名的结果等价于*&ar1[0],虽然ar1大小为24,但解引用时依然会返回第一个元素的值

     }

}

void copy_array(void) {

     double ar1[LEN] = { 1.0,2.0,3.0 }; // 使用define常量声明数组,经过编译之后所有LEN会被替换为数字3,所以在运行时不存在常量LEN,数组声明为ar1[3]

     double ar2[3],ar3[3],ar4[4]; // 使用常量声明数组

     copy_array1(ar1, LEN, ar2); //使用LEN和使用数字3等价

     copy_array2(ar1, sizeof(ar1)/sizeof(ar1[0]), ar3); //sizeof(ar1)是数组的大小,单位字节,数组占3个double即3*8字节,sizeof(ar1[0])是数组第一个元素的大小,元素为double类型,所以占8字节,所以3*8/8为3个元素的长度

     copy_array3(ar1, &ar1[3], ar4); // 数组名ar1实际上是一个地址常量,该地址实际是该数据对象/数组在内存中对应的块的首位的地址(24字节*8=192位的首位的地址),ar1 == &ar1[0] 为true,&ar1[0]是该double变量/数据对象在内存中对应的块的首位的地址(8字节*8=64位的首位的地址),而ar1[0]是数组ar1首元素,所以ar1[0]元素在内存中位于ar1数组的最前面,ar1[0]的8字节的首位的地址和ar1的24字节的首位的地址是同一个地址,也就是说这里使用ar1[0]的地址既可以用&ar1[0]表示也可以用ar1表示,因为传参的实参为那一位的地址,无论主调函数是怎么使用这一位的,被调函数只接受了这一位的地址,没有接收主调函数赋予的含义,但ar1不是&ar1[0],sizeof(ar1)为24,而sizeof(&ar1[0])为8,这是因为类型/单位不同,ar1是一个包含3个double元素的数组,单位是3个double大小即3*8,而ar1[0]是一个double类型的元素,单位是1个double即8字节,函数定义中使用和元素同类型的指针来接收这个地址,所以接收到的指针是同类型(double)即单位为8字节的指针,并不包含数组长度(因为数组没有完整传进来),所以需要一个参数来控制指针移动的范围,len表示指针向后移动(增加)的次数,在该次数之内的范围是数组的范围,end_next表示指针向后移动到该地址之前都是数组的范围,&ar1[3]实际也只是将数组范围外的第一位的地址传参给被调函数,被调函数中使用double*来接收这一位置,(可以理解为)将其解释成数组范围外首元素的地址

     show_array(ar1, LEN);

     show_array(ar2, LEN);

     show_array(ar3, LEN);

     show_array(ar4, LEN);

    

}

void copy_array1(double src[], int len, double dest[]) {

     int i;

     for ( i = 0; i < len; i++)

     {

          dest[i] = src[i];

     }

     // 数组的修改是直接在原地址上进行的,所以不需要额外返回数组

}

void copy_array2(double* src, int len, double* dest) {

     int i;

     for ( i = 0; i < len; i++)

     {

          *dest++ = *src++; // 判定顺序为++(后缀递增)优先级高于*,后缀递增先使用再自增,指向double的指针每次递增或+1即向后移动一个double长度即8字节即移动64位,假设dest的值(指向的地址)为F4A0,那么dest++后的值为F4A8,这里dest的地址不会变化即&dest是常量(指针变量的常量地址),而dest的值变了即*&dest改变了,而*dest是dest值指向的地址的值,使用指针赋值的思路:数组是连续的内存空间,用指针src指向了该连续空间/地址的首个元素的地址,将该地址的值赋给dest指向的地址(同为数组的首元素地址),每次赋值之后两个指针都向后移动一个单元,相当于数组索引增加一位,总共有len个单位,所以总共进行len次赋值后递增

     }

}

void copy_array3(double* src_start, double* src_end_next, double dest[]) {

     int i;

     for (i = 0; src_start + i < src_end_next; i++) {

          dest[i] = *(src_start + i); // *src_start代表指向地址的值,src_start+1代表指向地址的下一个单位的地址,*(src_start+1)即下一个地址的值,等价于src_start[1],*src_start等价于*(src_start+0),而dest[i]也可以写为:*(dest+i)

     }

     // 在形参定义上,指针和一维数组是等价的,double* dest和double dest[]是完全相同的

}

void show_array(const double ar[], int len) { // 如果函数不需要对传入的数组进行修改,可以将形参声明为const只读,这使得通过指针ar调用地址的值的时候无法修改该值,但这不会影响原数组,无论原数组是否const

     int i;

     printf("[");

     for (i = 0; i < len-1; i++)

     {

          printf(" %.1f,", ar[i]);

     }

     printf(" %.1f]\n", ar[len - 1]);

     //ar[0] = 1.0;  无效,因为const double ar[]指针不能修改指向的地址的值

     double* const ar2 = ar; // 将const写在指针名前面,使指针的值不能修改,即指针指向的地址

     //ar2++;  无效,ar2不能修改指向的地址

     ar2[0] = 1.0; // 有效,ar2[0]等价*ar2,虽然不能修改指向的地址,但是可以修改指向地址的值

     const double* const ar3 = ar; //两个const,限制ar3既不能修改指向的地址,也不能修改指向地址的值

}

void show_max1(void) {

     int ar[] = { 5,2,6,3,1 }; //使用{}初始化数组而不指定数组长度的情况下,编译器会计算{}的长度赋值给数组,这使得程序运行之前就知道应该为这个数组预留多少空间

     show_max2(ar, sizeof(ar) / sizeof(*ar)); //*ar==ar[0]

}

void show_max2(int ar[], int len) {

     int max;

     for (max = 0; len > 0; len--)

     {

          max = max > ar[len - 1] ? max : ar[len - 1]; // index应为0~len-1,所以第一次循环len为原值索引为长度-1,最后一次循环len为1索引为1-1

     }

     printf("max = %d", max);

}

int show_max_index1(void) {

    printf("index = %d", show_max_index2((double[]) { 2.0, 5.0, 3.0, 6.0, 1.0 }, 5)); // 复合字面量,类似int常量20、char常量'a',用(type [size]){element1,element2....}的形式表示数组常量,不需要起变量名,如这里的 (double[]){2.0,5.0...} ,数组的长度不写的话编译器会计算{}的长度来补上

}

int show_max_index2(double ar[], int len) {

     int max = 0;

     for (int i = 1; i < len; i++)

     {

          max = ar[max] > ar[i] ? max : i;

     }

     return max;

}

void array2d(void) {

     double ar[3][4] = {

     {1.0,2.0,3.0,4.0},

     {2.0,3.0,4.0,5.0},

     {3.0,4.0,5.0,6.0}

     }; //二维数组,ar有三个数组元素,每个数组元素有四个double元素,将每个子数组单独写一行,ar数组看作是一个3行4列的矩阵,一行row为一个一维数组(二维数组的元素ar[i]),一列col为所有一维数组的同下标元素(ar[][j])

     // 一维数组本身是连续内存空间,连续的地址依次存放每个元素的值

     // 二维数组同样,本身是连续内存空间,连续的地址依次存放每个数组元素本身,所以二维数组即更大的一块连续的内存空间,从第一个地址开始,连续存放第一个子数组的每一个元素,之后再存放第二个子数组,以此类推

     // 二维数组不是引用类型,C没有引用类型,二维数组中实实在在存放着一维数组本身,所以二维数组名ar地址常量的值和ar[0]首子数组地址常量的值和&ar[0][0]首子数组的首元素的地址相同,都是那一位的地址

     // 但大小不同,ar的大小为整个二维数组的3*4*8,ar[0]子数组的大小为4*8,ar[0][0]元素的大小为8

     // 使用{}初始化声明二维数组同样可省略长度3、4,编译器会计算后补上

     double dest[3][4]; // 没有初始化数组的值,3、4不能省略,因为编译器不知道数组多大

     copy_array1(ar[0], 4, *dest); //拷贝一维数组,*dest和dest[0]等价,而**dest和dest[0][0]等价

     copy_array2(ar[1], 4, *(dest + 1)); //*(dest+1)和dest[1]等价

     copy_array3(ar[2], ar[2] + 4, *dest + 8); //ar[2]是&ar[2][0]的地址,ar[2]+4即&ar[2][4]的地址,这和ar+2==&ar[2]同理,而*dest是首子数组的首元素的地址&dest[0][0],单位是double8字节,因为数组是内存连续的,所以&dest[0][0]的下一个是&dest[0][1],&dest[0][3]的下一个是&dest[1][0],所以*dest的下8个是&dest[2][0]

     for (int i = 0; i < 3; i++)

     {

         show_array(dest[i], 4);

     }

     array2d_alter(dest, 3);

     array2d_show(dest, 3);

}

void array2d_alter(double ar[][4], int len) { //多维数组作为形参时,第一个[]内的值会被编译器忽略,之后的[]必须声明值,因为多维数组的单位是子数组的大小,指针ar[][4]每++都会移动一个子数组即4个double长度,这个长度是必须在编译阶段声明的

     for (int i = 0; i < len; i++) //len是行row

     {

         for (int j = 0; j < 4; j++) //4是列col

         {

              ar[i][j] *= 2; //将每个double元素的值翻倍

         }

     }

}

void array2d_show(double ar[][4], int len) {

     // 形参中声明的double ar[][4]是二维数组的指针,在语句块内声明二维数组的指针需要写作 double(*ar)[4];

     double(*arr)[4] = ar; //将二维数组指针ar的值赋给arr,这时两个指针指向相同的地址,单位也相同

     double* arra[4]; //[4]的优先级高于*,所以这会先创建一个包含4个元素的数组,然后运行*,数组的元素是指针,然后运行double,指针指向double,所以arra是包含四个double指针的数组,arra==&arra[0]是常量而非变量,不能作为左值,而ar和arr、arra[0]是指针变量,可以作为左值

     arra[0] = ar[0]; //arra[0]是指向double的指针,而ar[0]是double一维数组,也是&ar[0][0]首元素double元素的地址,这两个的单位都是double,可以赋值

     *(arra + 1) = *(ar + 1); //arra是&arra[0]的地址常量,单位为指针的大小,因为是存放指针的数组,+1只会移动一个指针类型大小,在指针数组的区域内向后移动了一个单位,ar是指针,是变量,ar的值是二维数组的[0][0]的地址,ar的单位是[4]四个double大小,+1是从二维数组区域内的[0][0]地址向后移动了一个单位

     for (int i = 0; i < len; i++)

     {

         for (int j = 0; j < 4; j++)

         {

              printf("%.1f ", ar[i][j]);

         }

         putchar('\n');

     }

}

void sub_array(void) {

     double ar[7] = { 1.0,2.0,3.0,4.0,5.0 }; //声明的长度不能小于{}的长度,如果大了,那么数组后侧空出来的元素会初始化为0,即{1,2,3,4,5,0,0}

     double sub_ar[3]; //声明一个3元素的数组

     copy_array1(ar, 3, sub_ar); //将ar[0]的地址传给函数,指定拷贝长度3,所以sub_ar实际会得到{1.0,2.0,3.0}这前三个元素的值

     show_array(sub_ar, 3);

     copy_array2(ar + 2, 3, sub_ar); //将ar[2]的地址传给函数,指定拷贝长度3,所以sub_ar实际会得到{3.0,4.0,5.0}中间三个元素的值

     show_array(sub_ar, 3);

}

void array_vla(void) {

     int row = 3;

     int col = 5;

     //int ar[row][col] = { 1,2,3,4,5,6 };  //变长数组,使用变量声明数组的长度,因为编译器在编译阶段不知道变量在这一行时的确切值,所以数组的实际大小只有到运行程序时才能确定,MSVC不支持变长数组,在C11标准中规定变长数组VLA为可选项,不再必须实现

     //这里假设程序支持变长数组,那么ar有三个数组元素,每个数组元素有5个int元素,而初始化{}只显示了6个元素,则从前往后依次赋值,6个元素之后的所有位置都初始化为0,所以ar[0]是{1,2,3,4,5},ar[1]是{6,0,0,0,0}

}


C语言数组和指针的评论 (共 条)

分享到微博请遵守国家法律