首页 > 其他分享 >05. C语言数组

05. C语言数组

时间:2024-05-04 19:23:12浏览次数:39  
标签:struct 05 int 元素 C语言 数组 长度 结构


数组用于将多个数据集中存储,方便管理,此文将集中存储任何类型数据的语句都称为数组,数组根据存储数据的类型和方式分为同型数组、结构体、共用体、枚举。

 


【同型数组】

同型数组也直接称为数组,用于存储多个类型相同的数据,数组内的数据称为数组元素,数组元素占用连续的虚拟地址,每个元素都有一个数组内部编号,称为数组下标,数组元素通过“数组名+下标”的方式调用,下标从0开始分配,第一个元素下标为0。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};      //int指定元素类型,a为数组名,[]符号内指定元素个数,{}符号内设置数组元素的值
    a[0] = 0;                    //使用“数组名[下标]”调用数组元素
    
    int b[5] = {1,2,3};          //只为部分元素赋值,未赋值的元素编译器自动赋值为0
    
    int c[] = {1,2,3,4,5};       //定义数组并赋值,可以不指定长度,编译器以赋值元素数量确定数组长度
    
    int d[5];                    //定义数组不赋值,但是必须指定数组长度
    
    const int e[3] = {1,2,3};    //常量数组,不能修改内部元素
    
    return 0;
}

注意:数组下标从0开始计算,数组元素数量从1开始计算,比如 int a[5],数组a有5个元素,下标最大为4。


调用数组元素时,下标可以使用常量、变量两种数据指定,变量的值可以在程序执行期间临时确定,默认情况下两种方式都不会进行数组越界访问检查,若程序需要使用变量指定数组下标,则应该首先判断变量的值,防止超过数组长度导致越界访问。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    unsigned int b;
    
    printf("输入访问下标\n");
    scanf("%d",&b);            //终端输入函数,输入b的值,以回车结束
    
    /* 数组下标从0开始,下标上限为数组长度-1 */
    if(b > 4)
    {
        printf("禁止越界访问\n");
    }
    else
    {
        printf("a[%d]的值为%d\n", b, a[b]);
    }
    
    return 0;
}

使用变量作为下标时,编译器使用如下指令调用数组元素:

mov  edx, DWORD PTR [rbp+rax*4-0x20]

rbp-0x20 为数组的虚拟地址,rax为数组下标,4为数组元素长度,rax乘以4定位到元素所在数组的内部地址,rbp-0x20+rax*4定位到数组元素的虚拟地址。

多维数组

数组可以嵌套定义,在一个数组中存储其它数组,这种数组称为多维数组,多维数组存储的其它数组的长度和元素类型需要相同。
二维数组的元素是一维数组,三维数组的元素是二维数组,以此类推,还可以有四维数组。

#include <stdio.h>
int main()
{
    /* 定义二维数组,其中包含3个一维数组,一维数组的长度为5 */
    int a[3][5] =
    {
        {1, 2, 3, 4, 5},
        {11, 12, 13, 14, 15},
        {21, 22, 23, 24, 25}
    };
    
    /* 二维数组使用两个下标调用元素,这里调用第1个一维数组的第2个元素 */
    a[0][1] = 0;
    
    /* 定义三维数组,内部包含3个二维数组,每个二维数组包含4个一维数组,每个一维数组包含5个元素 */
    int b[3][4][5] =
    {
        {{0,0,0,0,0}, {0,0,0,0,0}, {0,0,0,0,0}, {0,0,0,0,0}}, 
        {{1,1,1,1,1}, {1,1,1,1,1}, {1,1,1,1,1}, {1,1,1,1,1}},
        {{2,2,2,2,2}, {2,2,2,2,2}, {2,2,2,2,2}, {2,2,2,2,2}}
    };
    
    /* 调用三维数组元素 */
    b[2][3][4] = 0;

    return 0;
}

有些教材将二维数组比喻为一张表,将三维数组比喻为一个立体结构,其实这样解释是不准确的,甚至基于这个思路就无法理解三维以上的数组。
二维数组将多个一维数组集中存储,三维数组将多个二维数组集中存储,四维数组将多个三维数组集中存储,内存单元并没有分为平面结构和立体结构,多维数组的存储也是线性的,所有的数组元素全部依次排列在虚拟地址中。

比如定义一个二维数组:

int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}};

内部元素的存储顺序如下:

a[0][0]
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]
a[2][0]
a[2][1]
a[2][2]

使用变量调用二维数组元素

#include <stdio.h>
int main()
{
    int a[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
    unsigned int b,c;
    scanf("%u%u", &b, &c);      //输入变量b、c的值
    
    if(b<3 && c<3)
    {
        printf("元素值为:%d\n", a[b][c]);    //使用两个变量调用数组元素
    }
    
    return 0;
}

调用二维数组元素步骤如下:
1.变量b乘以12,定位到二维数组内部地址。
2.变量c乘以4,定位到一维数组内部地址。
3.二维数组地址+b+c。

汇编代码如下:

mov    eax,DWORD PTR [rsp+0x8]           ;变量b写入eax
mov    edx,DWORD PTR [rsp+0xc]           ;变量c写入edx
lea    rax,[rax+rax*2]                   ;b*3
add    rax,rdx                           ;b+c
mov    esi,DWORD PTR [rsp+rax*4+0x10]    ;rsp+0x10为二维数组地址,rax*4为元素占用的数组内部地址

编译器对调用二维数组元素的复杂数学运算进行了优化,首先b乘以3,之后与c相加,此时b和c都需要再乘以4,所以让b+c的结果统一乘以4,再与二维数组地址相加得出元素具体地址。

 


【字符串】

存储字符编码的char类型数组称为字符串,字符串变量的元素经常需要修改,不同时期的值会占用字符串不同的长度,为了确定字符串使用了哪些数组元素,C语言规定字符串需要在已用元素之后添加一个空字符(字符编码为0),程序通过空字符的位置确定字符串有效字符长度。

空字符只在程序内部使用,将字符串存储在文件中时一般不存储空字符。

#include <stdio.h>
int main()
{
	char a[10] = "ali";    //使用多个字符赋值,字符放在""符号内,编译器转换为对应的UTF8编码,未赋值元素自动赋值为0
	char b[3] = "ali";     //错误,没有剩余位置放置空字符
	
	char c[50] = "阿狸\0喜羊羊";    //\0表示空字符
	printf("%s\n", c);            //输出“阿狸”,空字符及之后的字符不会使用

	char d[10] = {1, 2, 3};       //为元素单独赋值,一般表示当做普通数组,而非字符串

	return 0;
}

若字符串的长度太大,不方便单行存储,可以使用多行存储,有以下两种方式。

#include <stdio.h>
int main()
{
    /* 使用\符号换行存储,\符号以及换行字符不算入字符串元素,但是多行之间的空格算入字符串,此方式需要保证多行之间没有空格 */
    char a[50] = "阿狸\
喜羊羊";
    
    /* 使用多组""符号换行存储,多组""符号之间的换行和空格都不算入字符串 */
    char b[50] = "阿狸"
    "喜羊羊";
    
    return 0;
}

转义字符

有些字符起控制作用,不用于显示,比如回车、换行,这些字符无法直接定义,可以使用转义字符表示。
有些字符与C语言关键词重复,比如 \ ' " ? 符号,这些字符也不能直接编写,需要使用转义字符表示。

转义字符以\符号开始(注意不是/符号),之后定义转义字符类型,常用转义字符如下:

\r,表示回车,将打印位置移动到最左侧
\n,表示换行,将打印位置向下移动一行
\b,表示退格,将打印位置向左移动一个字符
\0,表示空字符
\t,表示水平空白
\v,表示垂直空白
\',表示 ' 符号
\",表示 " 符号
\?,表示 ? 符号
\\,表示 \ 符号

#include <stdio.h>
int main()
{
    char a[] = "ali\n";    //"ali\n"会被编译器转换为如下字符编码:97,108,105,10,0
    printf(a);             //输出ali并换行
    
    return 0;
}

 


【结构体】

结构体用于存储一组类型不同的数据,可以理解为异型数组,因为结构体成员的类型不同、长度不同,所以结构体成员不能使用下标的方式调用,每个结构体成员都有自己的名称,使用“结构体名+成员名”的方式调用结构体成员。

编写代码时经常需要使用多个成员类型相同的结构体,比如存储一个班级学生的属性信息,此时需要使用几十个内部成员相同的结构体,为了简化结构体定义代码,C语言将结构体的定义分为两个部分:声明部分、实体部分。
1.声明部分,定义结构体成员的类型、名称,声明是一种伪指令,不会被编译为任何数据,作用只是简化定义多个成员类型相同的结构体、无需重复指定成员类型、成员名称,此部分也称为结构体类型,表示其用于确定结构体的整体长度以及内部成员的类型。
2.实体部分,根据声明部分定义具体的结构体,也称为结构体实例。

#include <stdio.h>
int main()
{
	/* 使用struct关键词定义结构体的声明部分和实体部分,zoo为声明部分的名称,成员的类型和名称放在{}符号内,声明语句以;符号结尾 */
	struct zoo
	{
		char name[50];      //学生姓名
		char gender[10];    //学生性别
		float score;        //考试分数
		int rank;           //考试排名
	};
	
	struct zoo ali = {"阿狸", "男", 90, 3};    //使用声明定义结构体实例
	ali.score = 91;                           //使用“实例名.成员名”调用内部成员
	
	struct zoo taozi = {"桃子", "女", 92};     //只为部分成员赋值时,剩余成员编译器默认赋值为0,这一点与同型数组相同
	
	const struct zoo xyy = {"喜羊羊", "男", 95, 1};   //常量结构体,定义时赋值,之后不能修改
	
	struct zoo zoo[3] = {ali, taozi, xyy};           //结构体作为数组元素
	printf("%s\n", zoo[0].name);                     //调用方式
	
	struct zoo x = ali;    //结构体可以引用其它同类型实例赋值
	x = xyy;               //结构体可以整体修改,若结构体中定义了常量成员则禁止整体修改
	
	return 0;
}


可以使用typedef关键词为结构体类型设置另一个名称。

#include <stdio.h>
int main()
{
    typedef struct zoo
    {
        char name[50];
        int age;
    } student;
    
    student ali = {"阿狸", 8};
    student xyy = {"喜羊羊", 9};
    
    return 0;
}

匿名结构体

声明部分可以不设置名称,称为匿名结构体类型,匿名结构体类型需要在声明时定义所有实例,之后不能再定义新的实例。

#include <stdio.h>
int main()
{
    struct
    {
        char name[50];
        int age;
    } ali={"阿狸", 8}, xyy={"喜羊羊", 9};

    return 0;
}

有名称的结构体类型也可以使用这种简化方式定义实例。

嵌套结构体

结构体内部可以定义另一个结构体实例,这种功能很好理解,另外结构体的声明部分也可以嵌套定义,从而将结构体内的数据继续分类管理。

#include <stdio.h>
int main()
{
    struct zoo
    {
        char name[50];      //姓名
        char gender[10];    //性别
        
        /* grades记录各科考试成绩,嵌套结构体声明需要直接定义出所有实例,确定占位长度,但是不能在内部直接赋值 */
        struct grades
        {
            float score;   //分数
            int rank;      //排名
        } math, chinese, english;
    };
    
    struct zoo ali = {"阿狸", "男", {90,1}, {80,2}, {70, 3}};
    
    printf("阿狸的数学分数为:%f\n", ali.math.score);
    
    return 0;
}

功能等同于如下代码,只不过下面代码中的 struct grades 是公用的,struct zoo 之外的代码也可以使用。

#include <stdio.h>
int main()
{
    struct grades
    {
        float score;
        int rank;
    };
    
    struct zoo
    {
        char name[50];
        char gender[10];
        
        struct grades math, chinese, english;
    };
    
    struct zoo ali = {"阿狸", "男", {90,1}, {80,2}, {70, 3}};
    
    printf("阿狸的数学分数为:%f\n", ali.math.score);
    
    return 0;
}

结构体的实际长度

结构体在内存中存储时的长度并非所有成员长度的总和,因为结构体成员的长度不同,地址对齐值也不同,多个成员中间往往会有地址对齐额外占用的内存单元,所以结构体的实际长度会大于内部成员长度的总和,可以使用sizeof查询结构体的长度。

#include <stdio.h>
int main()
{
    struct k
    {
        int i;
        char c;
    };
    
    printf("k的长度为%d字节\n", sizeof(struct k));    //在x86-64计算机中k类型的长度为8字节
    
    return 0;
}

实现数组整体修改

C语言规定数组只能在定义时整体赋值,之后不能整体修改,两个数组之间也不能引用赋值,即使两个数组的元素类型、元素数量都相同也不行,但是同类型的结构体实例之间可以互相引用赋值,若需要整体修改数组,除了使用库函数或者使用循环代码外还可以将数组放在结构体内。

#include <stdio.h>
int main()
{
    typedef struct
    {
        char name[50];
    } string;
    
    /* 注意:实际项目中数组成员赋值应该做越界访问检查,这里简化代码,不做检查 */
    
    string ali = {"阿狸"};
    string xyy = {"喜羊羊"};
    
    string x = ali;
    printf("%s\n", x.name);    //输出阿狸
    
    x = xyy;
    printf("%s\n", x.name);    //输出喜羊羊
    
    return 0;
}

变长数组成员

结构体内的数组成员可以在声明时不指定长度,而是在定义实例时确定长度,长度可以使用变量指定,若多个同类型结构体实例为变长数组成员设置了不同的长度,则他们本质上是不同类型的结构体实例,他们的长度不同,他们之间互相引用赋值时会产生错误。

使用注意事项:
1.变长数组成员不能单独出现,否则结构体长度默认为0,不能编译。
2.变长数组成员最多定义一个,并且只能定义在结构体成员的末尾。
3.使用sizeof计算结构体长度时,变长数组成员不计入结构体长度。

此类结构体实例不能定义为函数局部数据,不能存储在栈中,存储方式如下:
1.若变长数组成员可以在编译期间确定长度,可以将结构体实例定义为全局成员、静态局部成员,放在全局数据区存储。
2.若变长数组成员不能在编译期间确定长度,需要在程序执行期间向操作系统申请内存存储,注意结构体总长度需要加上变长数组成员长度。

#include <stdio.h>
struct zoo
{
    int age;
    char name[];
};
struct zoo ali = {8, "阿狸"};
struct zoo xyy = {9, "喜羊羊"};
int main()
{
    printf("%s:%d岁\n%s:%d岁\n\n", ali.name, ali.age, xyy.name, xyy.age);
    return 0;
}

位字段

结构体也可以用于存储自定义长度的数据,这种结构体称为位字段,编译器会将多个自定义长度的数据进行整合,最先定义的数据排在最低位,最后定义的数据排在最高位,中间不会进行地址对齐,一般用于设置计算机底层功能相关数据。

#include <stdio.h>
int main()
{
    struct k
    {
        int a : 4;    //int表示成员a最大可以设置为32位长度,而非固定为32位长度,这里设置为4位二进制数字长度
        int b : 2;    //b长度2位
        int c : 2;    //c长度2位
        int d : 8;    //d长度8位,结构体总长度16位,等于2字节
    };
    
    struct k k1 = {1,2,1,5};    //为每个自定义长度数据赋值
    
    printf("0x%hX\n", k1);      //输出0x561,转二进制 = 0101 01 10 0001,按自定义的4个长度将二进制数据分为4组观察,每组的值对应k1设置的值
    
    return 0;
}

 


【共用体】

使用汇编语言编写代码时,可以使用指令随意操作一段内存数据,比如可以进行如下操作:
1.重复使用一段内存,一个数据不再使用后,其占用的内存空间用于存储其它数据。
2.将一段内存中的数据当做任意类型使用,比如有符号整数、无符号整数、浮点数、同型数组、异型数组。

C语言不像汇编那样灵活,所以C语言提供了共用体,使用共用体可以实现上述两种功能,当然使用指针一样可以实现随意操作内存,从而实现共用体的功能,但是使用指针太过繁琐。

共用体的使用方式类似结构体,也是先声明后使用,共用体的声明部分用于设置共用体可以存储、转换的数据类型,使用共用体转换数据类型时只能在声明中的类型之间转换。

#include <stdio.h>
int main()
{
	/* 使用union关键词定义共用体的声明和实体部分,共用体的长度为其中最大成员的长度 */
	union aliun
	{
		int i;
		unsigned int u;
		float f;
	};
	
	union aliun ali1;          //定义共用体实例,不可直接赋值
	
	ali1.i = -1;               //通过成员名确定共用体的类型,之后为共用体赋值
	printf("%d\n", ali1.i);    //共用体作为有符号int类型使用,输出-1
	printf("%u\n", ali1.u);    //共用体作为无符号int类型使用,输出4294967295
	
	ali1.f = 3.14;             //之前的数据不再使用后,其占用的内存再次利用,存储一个浮点数
	printf("%f\n", ali1.f);
	
	return 0;
}

实现某些底层功能时,可能需要将一个数据的类型在数组与单个数据之间进行转换,此时可以使用共用体。

#include <stdio.h>
int main()
{
	/* 定义匿名共用体 */
	union
	{
		char c[4];
		int i;
	} x;
	
	x.c[0] = 1;
	x.c[1] = 2;
	x.c[2] = 3;
	x.c[3] = 4;
	
	printf("0x%x\n", x.i);    //转换为int类型,输出0x4030201,实际存储值为0x04030201
	
	return 0;
}

 


【枚举】

枚举,意为一一列举,将一个变量可以使用的值全部列举出来,之后此变量只能使用其中之一进行赋值,枚举用于限制一个变量的可用值,防止为变量设置一个不合适的值导致功能出错,这个限制是由编译器提供的,对程序进行逆向分析并修改时并不存在任何限制。

定义枚举时无需指定数据类型,枚举默认为int类型,并且不能修改为其它类型。

枚举同样是先声明后使用,声明部分用于设置枚举变量的可用值,每个可用值都有一个名称,之后通过声明部分定义枚举变量,为枚举变量赋值时只能使用声明中设置的可用值。

#include <stdio.h>
int main()
{
    enum alienum{m1=1, m2=2, m3=3};    //声明枚举,alienum为声明部分的名称,{}符号内设置可用值
    enum alienum a = m1;               //定义枚举变量,a为实体部分的名称,使用声明部分设置的可用值名称进行赋值
    enum alienum b = m2;
    a = m2;                            //修改枚举变量的值
    a = b;
    
    return 0;
}


声明部分设置的可用值等于定义的int常量,每个常量都有自己的名称,若只设置常量名而不为常量赋值,则编译器默认从0开始依次为每个常量赋值,声明部分的常量在语意上属于函数的局部常量,这些常量可以直接使用,并且不能与其它局部数据同名。

#include <stdio.h>
int main()
{
	int a;
	
	//enum alienum{a, b, c};    //错误,与已经存在的变量a同名
	enum alienum{m1, m2, m3};   //正确
	
	printf("%d\n%d\n%d\n", m1, m2, m3);    //输出0、1、2
	
	return 0;
}

 

标签:struct,05,int,元素,C语言,数组,长度,结构
From: https://www.cnblogs.com/alixyy/p/18172580

相关文章

  • 2024-05-04 css实现鼠标移动至盒子,盒子在约定时间内进行放大缩小
    放大缩小css@keyframesscaleAnimation{0%{transform:scale(1);}50%{transform:scale(1.2);}100%{transform:scale(1);}}完整代码:<!DOCTYPEhtml><htmllang="en"><head><metacharset=&q......
  • C# String.Split 将字符串按照指定的分隔符分割成一个字符串数组
    以下两种方式都可以分割字符串string[]arr=s.Split('\n');string[]arr=s.Split(newchar[]{'\n'},StringSplitOptions.RemoveEmptyEntries);区别:string[]arr=s.Split('\n');:这种方式使用单个字符作为分隔符,将字符串s按照换行符('\n')进行分割。但是,此......
  • 2024-05-04:用go语言,给定一个起始索引为0的字符串s和一个整数k。 要进行分割操作,直到字
    2024-05-04:用go语言,给定一个起始索引为0的字符串s和一个整数k。要进行分割操作,直到字符串s为空:选择s的最长前缀,该前缀最多包含k个不同字符;删除该前缀,递增分割计数。如果有剩余字符,它们保持原来的顺序。在操作之前,可以修改字符串s中的一个字符为另一个小写英文字母。在最佳情......
  • 浙大版C语言程序设计习题11-17
    点击查看代码typedefstructNODE{intdata;structNODE*next;}NODE,*Linkedlist;//初始化头节点voidInit(Linkedlist&L){L=(NODE*)malloc(sizeof(NODE));L->next=NULL;}//尾插法创建链表LinkedlistCreateFromRear(LinkedlistL){NODE*rear=L;for......
  • 20240503
    T1NFLSOJP2023050961捉迷藏首先只需要考虑所有叶子,只要每个叶子都连向了另一个距离超过\(2\)的叶子,则符合要求。距离超过\(2\)等价于在不同的父亲下。则问题变为一堆点,每个点有颜色,同色点间没有边,异色点间两两有边,求最大匹配。结论:设点最多的颜色\(c\)有\(x\)个点,若......
  • C语言代码题
    C语言代码--练习题试写一个函数,计算字符串s中最大连续相同的字符个数。例如,若s为"aaabbbb",则返回值为4;若s为"abcde",则返回值为1。#include<stdio.h>/******************************************************************************functionname:max_sam......
  • 【C语言】---- 文件输入输出与文件管理函数
    1文件输入输出函数1.1打开和关闭文件fopen函数fopen是C标准库中用于打开文件的函数之一。它的原型定义在<stdio.h>头文件中,具体格式如下:FILE*fopen(constchar*filename,constchar*mode);这个函数接受两个参数:filename:一个以字符串形式表示的文件名,用于指定要......
  • UES-05-解构
    解构的作用方便从对象或者数组等数据结构中提取想要的数据。使用任何一种类型的解构,当=右边的值为null或undefined时,会抛出错误。对象解构通过在=左边使用{},在大括号内部写入以逗号分隔的=右边对象的属性名,则对应的属性名作为本地变量名,同时变量的值即为属性值。(......
  • 05_多元线性回归
    第5章多元线性回归5.1二元线性回归案例说明Cobb-Dougls生成函数:\[y_i=\alphak_i^{\beta}l_i^{\gamma}e^{\epsilon_i}\]两边同时取对数,可转换为线性模型:\[\lny_i=\ln\alpha+\beta\lnk_i+\gamma\lnl_i+\epsilon_i\]这就是二元线性回归模型。代码实现[[Chapter......
  • 20240503比赛总结
    T1[CF1279C]StackofPresentshttps://gxyzoj.com/d/hzoj/p/3686数据出锅了,100->40按题意模拟即可,可以发现,最优情况下,一定是将取出的数按后面的拿的顺序排序,O(1)取出,而在取之前未排序的,则需要花2k+1的时间排序并取出代码:#include<cstdio>#definelllonglongusingnamesp......