• C语言-数组


    1. 概念

    1.1 分类

    根据数据类型的不同:

    • 数值数值
    • 字符数组/字符串
    • 指针数组
    • 结构数组

    1.2 概念

    C语言数组在存储时,是行优先
    多维数组下,如二维数组,可以看成是嵌套:一个数组可以作为外部数组的一个元素
    C语言数组是静态的,不能插入或删除元素(数组一旦被定义,占用的内存空间就是不可改变的,但是可以修改和读取数值),如果要改变一个数组,就要再造一个数组

    1.3 数组类型

    注意:数组也是有类型的!!!

    int a[10]; // 数组a的类型是int [10]
    
    • 1

    1.4 数组与内存

    数组内存时连续的,(连续的内存为指针操作和内存处理提供了方便),数组可以作为 缓存 使用,(临时存储数据的一块内存)

    1.4.1 数组越界与溢出

    数组下标越界不会产生编译错误,只会在运行过程中产生错误

    int a[3]={1,2,3}; // printf a[3],如a[4,5,6]...以后不知道是什么值
    // 对于int a[3]={1,2,3,4,5};但是高级编译器会报错,或者
    // 对于下面这种 编译器也不会报错
    int a[3];
    for (int i=0;i<5;i++)
    {
        a[i]=i;
    }
    // 这里sizeof(a)==sizeof(int)*3
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    负数索引

    尽量不要使用负数索引,负数索引 指的是arr[0]的内存空间中的前一个元素

    越界与内存

    数组越界赋值,有可能修改其他内存空间中其他数据的值

    int main()
    {
    	// 下面的变量 可能在内存空间上是连续的
    	int var1=10;
    	int a[3];
    	int var2=30;
    
    	for (int i=-1;i<=4;i++)
    	{
    		a[i]=i;
    	}
    
    	for (int i=-1;i<=4;i++)
    	{
    		printf("%d\n",a[i]);
    	}
    
    	printf("%d\n",var1); // 可能变成了-1
    	printf("%d\n",var2); // 可能变成了 4
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    1.5 变长数组

    变长数组仍然是静态数据,一旦定义之后都是不可变的
    array[length]
    scanf("%d",&length);array[length]

    1.6 静态分配与动态分配

    #define MaxSize 10
    typedef struct
    {
        int data[MaxSize]; // 静态分配
        int length;
    }SqList;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    1.6.1 动态分配与malloc

    new是C++中的关键字
    malloc 返回的是被 动态分配 内存的首地址

    malloc 可以用来返回数组指针、结构指针等

    #include 
    #include 
    
    #define MAX 3
    
    using namespace std;
    
    int main()
    {
        double *ptd=(double *)malloc(MAX*sizeof(double));
    
        if (ptd==NULL)
        {
            cout<<"ERROR: memory created wrong!";
            exit(EXIT_FAILURE);
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    例子2

    #include 
    
    #define InitSize 10
    typedef struct 
    {
        int *data; //动态分配
        int MaxSize;
        int length;
    }SeqList;
    
    void InitList(SeqList *L)
    {
        L->data=(int*)malloc(InitSize*sizeof(int));
        L->length=0;
        L->MaxSize=InitSize;
    }
    
    void IncreaseSize(SeqList *L,int len)
    {
        printf("%p\n",L->data); //00000000006916A0
        int *p=L->data; // 这是为了防止内存泄漏
        printf("%p\n",p); //00000000006916A0
        L->data=(int*)malloc((L->MaxSize+len)*sizeof(int)); //0000000000691700 ,就把L->data当做一个指针就行了
        printf("%p\n",L->data);
        for (int i=0;i<L->length;i++)
        {
            L->data[i]=p[i];
        }
        L->MaxSize+=len;
        free(p); // 这是为了防止内存泄漏
    }
    
    int main()
    {
        SeqList L;
        InitList(&L);
        // for (int i=0;i
        // {
        //    printf("%d\t",L.data[i]);
        // }
        // printf("\n%d\n",L.length);
        IncreaseSize(&L,5);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    1.6.2 malloc 与二维数组 – 还没看!!!

    https://blog.csdn.net/fengxinlinux/article/details/51541003

    1.6.3 calloc/realloc

    • void * calloc(size_t n,size_t size)

    在堆区分配n*size 字节的连续空间
    成功分配返回内存地址,失败则返回NULL

    n: 单元个数(相当于有多少个单位),size: 单位字节数(每个单元有多少个字节)

    • void * realloc(void *ptr,size_t size)

    对ptr指向的内存重新分配size大小的空间,size可大可小
    成功分配返回内存地址,失败则返回NULL
    realloc 之后id不变

    free(p ) 并不改变指针p的值,p依然指向以前的内存,为了防止再次使用该内存,将p=NULL

    #include 
    using namespace std;
    
    #define N 5
    #define N1 7
    #define N2 3
    
    int main()
    {
        int *ip;
        int *large_ip;
        int *small_ip;
    
        if ((ip=(int *)malloc(N*sizeof(int)))==NULL)
        {
            cout<<"malloc ERROR";
            exit(1);
        }
    
        for (int i=0;i<N;i++)
        {
            ip[i]=i;
            cout<<ip[i]<<endl;
        }
    
        // cout<
        // (int *)realloc(ip,N1*sizeof(int));
        // cout<
        
        if ((large_ip=(int *)realloc(ip,N1*sizeof(int)))==NULL)
        {
            cout<<"realloc large ERROR";
            exit(1);
        }
    
        // 两个指向同一块内存
        cout<<ip<<endl; // 0x661f50
        cout<<large_ip<<endl; // 0x661f50
    
        for (int i=N;i<N1;i++)
        {
            large_ip[i]=i;
        }
        for (int i=0;i<N1;i++)
        {
            cout<<large_ip[i]<<" "<<ip[i]<<endl;
        }
    
        if ((small_ip=(int *)realloc(large_ip,N2*sizeof(int)))==NULL)
        {
            cout<<"realloc small ERROR";
            exit(1);
        }
    
        // 三个指向同一块内存
        cout<<ip<<endl; // 0x661f50
        cout<<large_ip<<endl; // 0x661f50
        cout<<small_ip<<endl; // 0x661f50
    
        // 缩小只会在有效范围内数值正常,越界会产生随机数
        for (int i=0;i<N2;i++) // N 或者N1都是错的
        {
            cout<<small_ip[i]<<" "<<ip[i]<<endl;
        }
    
        free(small_ip); // 只用释放small_ip 其他两个被系统回收
        small_ip=NULL;
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    1.7 数组与指针 – 见指针

    https://blog.csdn.net/L_fengzifei/article/details/126411708

    2. 数值数组

    创建数组:赋值个数小于开辟的内存空间,且剩余的元素会被初始化为0,
    对于整型,初始化为0
    对于浮点型,初始化为0.0
    对于字符数组,字符数组初始化为\0

    int a[10]={1,2,3};
    // 只能以循环的方式进行输出
    for (int i=0;i<10;i++){
        printf("%d\n",a[i]);
    }
    
    // 方法2
    int a[]={1,2,3,4}; // 自动计算需要开辟的内存空间
    
    // 比较常用的写法
    int a[10]={0};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    数组越界不会被编译器检测出来错误
    下面还有一个越界的例子 “2.1 数值数组与sizeof”

    int a3[3];
    for (int i=0;i<3;i++)
    {
        a3[i]=i;
    }
    
    for (int i=0;i<3;i++)
    {
        printf("%d\t",a3[i]); // 0,1,2
    }
    
    printf("\n");
    
    // 越界不会报错!!!
    // 但返回的是随机值
    for (int i=0;i<10;i++) 
    {
        printf("%d\t",a3[i]); // 0,1,2,3,3... 随机数
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    结构体之间可以进行赋值操作
    数组之间不可以进行赋值操作

    struct1=struct2
    // arr1=arr2 // 这种不行
    
    • 1
    • 2

    2.1 数值数组与sizeof

    越界也会按照开辟的内存,计算sizeof()

    // 完全不赋值
    int a[10];
    int* p=a;
    printf("%d\n",sizeof(a)); //40
    printf("%d\n",sizeof(p)); //8
    
    // 部分赋值
    int a2[10]={1,2,3};
    int* p2=a2; 
    printf("%d\n",sizeof(a2)); //40 , 对应的是开辟的数组
    printf("%d\n",sizeof(p2)); //8
    
    // 对于越界赋值,编译器不会出错
    // 但是后面访问的时候,并不会按越界赋值的数值显示,显示的依然是随机数
    int a3[3];
    for (int i=0;i<5;i++)
    {
        a3[i]=i;
    }
    int* p3=a3;
    printf("%d\n",sizeof(a3)); // 12
    printf("%d\n",sizeof(p3)); // 8
    
    /* == 例子2 == */
    // 动态计算
    int a4[]={1,2,3};
    printf("%d\n",sizeof(a4)); //12,计算的还是对应的初始化开辟的内存
    for (int i=0;i<5;i++)
    {
        a4[i]=i; // 越界赋值,编译器不报错
    }
    printf("%d\n",sizeof(a4)); // 12
    
    for (int i=0;i<5;i++)
    {
        printf("%d\n",a4[i]); // 越界打印不会报错,打印的依然是随机数
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    2.2 一维数组与指针

    数组指针:是指向数组的指针
    指针数组:数组中存放的是指针

    数组名可以认为是做一个指针,指向数组的第0个元素,但是数组名不可变,数组名本身是常量,所以 数组本身是指针这个说法不准确!!!
    第0个元素的地址称为数组的首地址
    数组指针 指向的是数组中的一个具体元素,而不是整个数组!!!

    指向数组的指针(数组指针),与数组名不同,数组名不可以改变,而数组指针可以改变
    注意: ++具有inplace操作
    (下面的具有 右结合性)
    *p++ 等价于*(P++),不能使用*arr++,因为数组名不能改变
    *++p 等价于*(++P) 等价于*(p+1)
    (*p)++只对数值进行改变

    datatype *p=arr p+1相当于移动了一个datatype类型的字节

    int a[3]={1,2,4};
    int* p=a;
    // 下面三个是等价的
    printf("%p\n",p);
    printf("%p\n",a);
    printf("%p\n",&a[0]);
    
    // printf("",a++); // 这个是错误的,数组名是不能改变的
    printf("%d\n",*p++); // 1 ,*p赋值之后,再++ !!!
    printf("%d\n",*(p++)); // 1,*p赋值之后,再++ !!!
    printf("%d\n",*p+1); // 2 等价于(*p)+1
    printf("%d\n",*(p+1)); // 2 
    printf("%d\n",*(++p)); //2 
    printf("%d\n",*++p); //2 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    int main()
    {
        int a[3]={1,4,5};
    
        int *p=a;
     
        // printf("%d\n",*p++);
        // printf("%d\n",*p);
        printf("%d\n",(*p)++);
        printf("%d\n",*p);
        // printf("%d\n",*(p++));
        // printf("%d\n",*p);
    
        for (int i=0;i<3;i++)
        {
            printf("%d\n",a[i]);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    重要

    []符号具有取内容的作用
    a[i],*(a+i),p[i], *(p+i) 这四个是等价的
    &a[i],(a+i),&p[i], (p+i) 这四个是等价的

    int a[3]={1,2,3};
    for (int i=0;i<3;i++)
    {
        printf("%d,%d,%d,%d\n",a[i],*(a+i),p[i],*(p+i));
        printf("%p,%p,%p,%p\n",&a[i],(a+i),&p[i],(p+i));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2.1 数组名/数组名地址

    int main()
    {
        int a[3]={1,2,3};
        printf("%p\n",a); // 0x61fe14 数组首元素地址 a的类型为 int
        printf("%p\n",&a); // 0x61fe14 整个数组的起始地址, 类型为int [3]
        printf("%p\n",&a[0]); // 0x61fe14
        printf("%p\n",a+1); // 0x61fe18 = 0x61fe14 + 0x04 首元素下一个元素的地址
        printf("%p\n",&a+1); // 0x61fe20 = 0x61fe14 + 0x0C(12) 跳过了整个数组
    
    	// int *p=&a; // 这种是错误的 类型不匹配
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    char str[10]="hello"
    func(str) 等价于 func(&str)
    
    • 1
    • 2

    2.3 memset/memcpy

    • memset

    将一块内存中的内容全部设置为指定的值
    void *memset(void *s,int c,size_t n) s为要填充的内存块的首地址,c为被设置的值(以 补码 进行字节初始化),n为要设置的字节数(内存块长度),返回一个指向该内存的指针

    注意:按字节对内存块进行初始化,所以不能用它将数组初始化为0和-1之外的其他值
    即,初始化时并不关心要初始化的数组的类型是什么,均以字节为单位进行初始化

    #include  // #include 
    // 例子1
    int a[4];
    memset(a,1,sizeof(a))
    1 == 0x00000001 以字节为单位初始化,则只能截取低80x01
    则每个元素初始化成0x01010101 也就是16,843,009
    
    // 例子2
    // 下面两个是等价的
    memeset(a,-1,sizeof(a)); // -1 在内存中的补码低8位是0x 1111 1111
    memeset(a,255,sizeof(a)); // 255 在内存中的补码低8位是 0x 1111 1111
    
    // 例子3
    // 清空结构体
    typedef struct Stu
    {
    	char name[20];
    	int age;
    }Stu;
    
    Stu stu1;
    memset(&stu1,0,sizeof(Stu))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    特别注意!!!
    memset函数是一个运行时函数,其在运行时才能确定其要设置的字节数,因此不能在全局作用域使用
    全局作用域下的变量进行初始化时,只能使用常量表达式或编译时常量(即具有静态存储期的变量),(所以memset不能在全局作用域下进行变量初始化)

    下面的例子是错误的!!!

    #include 
    #include 
    
    using namespace std;
    
    int arr[10];
    memset(arr,0,sizeof(arr));
    
    int main()
    {  
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    下面的例子是对的

    static int a[10];
    
    void func()
    {
    	memset(a,0,sizeof(a);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • memcpy

    内存复制
    void *memcpy(void *destin,void *source,unsigned n)source指向的地址为起点,将连续的n个字节数据,复制到以destin指定的地址为起点的内存中,返回一个指向destin内存的指针(注意返回的区别)
    https://blog.csdn.net/GoodLinGL/article/details/114602721

    char a[10] = "abcdefgh";
    unsigned n = 2;
    cout<<&a<<endl; // 0x61fe06
    void * p = memcpy(a+3, a, n); // p指向a,a
    // memcpy(a+3, a, n); // p指向a,a
    
    cout<<a<<endl; // abcabfgh
    cout<<&a<<endl; // 0x61fe06
    cout<<p<<endl; // 0x61fe09
    cout<<(char *)p<<endl; // abfgh
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    memove 允许数据重叠
    memcpy 不允许数据的内存空间存在重叠

    3. 多维数组

    创建数组:可以按行创建,也可以连续创建。部分赋值创建的时候,其他赋值元素默认初始化为0(注意部分赋值和完全不赋值随机初始化的区别,完全不赋值随机初始化的时候不是0)
    如果是全部赋值,第一维度可以不写
    可以理解为嵌套

    // 按行创建
    int a[2][3]={{1,2,3},{4,5,6}};
    
    // 按连续内存创建
    int a[2][3]={1,2,3,4,5,6};
    
    // 部分赋值
    int a[2][3]={{1},{2},{3}};
    int a[2][3]={{0,1},{0,1,2},{3}}
    
    // 第1维度自动推导
    int a[][3]={1,2,3}
    
    int a[][3]={1,2,3};
    for (int i=0;i<1;i++){
        for (int j=0;j<3;j++){
            printf("%d\n",a[i][j]); // 可以打印但是不能赋值
        }
    }
    
    // 注意!!!
    // 下面的初始化是错误的,无法实现自动推导
    // int a[][3];
    // for (int i=0;i<3;i++)
    // {
    //     for (int j=0;j<3;j++)
    //     {
    //         a[i][j]=i*j;
    //     }
    // }
    
    // 嵌套理解
    a[3][4] 中 a[0]是一个数组(也是一个一维数组名)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    3.1 二维数组与一维数组的关系

    a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]
    int arr[m] [n] arr数组有m个元素,每个元素的类型是一维数组int [n]

    3.2 多维数组与sizeof

    int a[2][3];
    for (int i=0;i<2;i++)
    {
        for (int j=0;j<3;j++)
        {
            a[i][j]=i*j;
        }
    }
    
    int (*p)[3]=a; 
    printf("%d\n",sizeof(a)); // 24
    printf("%d\n",sizeof(a[0])); // 12(因为相当于一个一维数组名)
    printf("%d\n",sizeof(p)); // 8
    printf("%d\n",sizeof(p[0])); // 12
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    3.3 多维数组与指针

    https://blog.csdn.net/L_fengzifei/article/details/126411708

    在这里插入图片描述

    a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]

    对于一维数组及其指针 type *p=arr p+1相当于移动了一个type类型的字节
    对于二维数组及其指针,p+1应该移动整个一维数组对应的字节,type (*p)[n],(*p) 表示指针,int [n] 表示指针指向的数据类型 也就是每行的数据类型

    int a[1][3]={1,2,3};
    for (int i=0;i<1;i++){
        // for (int j=0;j<3;j++){
        //     printf("%d\n",a[i][j]);
        // }
        printf("%p\n",a[i]); // &a[i][0] 与a[i] 等价
        printf("%p\n",&a[i][0]); // &a[i][0] 与a[i] 等价
        printf("%d\n",a[i][0]); // 这是数值
    }
    
    int a[2][3]={{1,2,3},{4,5,6}};
    // 下面三个是等价的 
    // *a[2][3] 可以看成是2个一维数组,每个一维数组的数组名是a[0],a[1]*
    printf("%p\n",*a);
    printf("%p\n",a[0]);
    printf("%p\n",&a[0][0]);
    
    
    // 下面是等价的
    int (*p)[3]=a;
    printf("%p\n",*(p+1));
    printf("%p\n",*(a+1));
    printf("%p\n",a[1]);
    printf("%p\n",&a[1][0]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    重要

    a=&a[0]
    a+i=&a[i]
    a[i]=&a[i][0]
    
    *a=a[0]
    *(a+i)=a[i]
    *a[i]==a[i][0]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    重要

    a[i]==*(a+i)==p[i]==*(p+i)
    a[i][j]==*(a[i]+j)==*(*(a+i)+j)==p[i][j]==*(p[i]+j)==*(*(p+i)+j)

    int (*p)[3]=a;
    for (int i=0;i<2;i++)
    {
        for (int j=0;j<3;j++)
        {
            printf("%d,%d,%d,%d,%d,%d\n",a[i][j],*(a[i]+j),*(*(a+i)+j),p[i][j],*(p[i]+j),*(*(p+i)+j));
        } 
    }
    
    /*  /// 补充 ///
        // 理解
        对于1维数组
        int a={1,2,3};
        int* p=a;
        a[1]==*(P+1)==*(a+1)==p[1]
        &a[1]==p+1==a+1
    
        // 二维数组
        // 看成嵌套的一维数组
        int a[][2]={1,2,3,4};
        int (*p)[2]=a; 
        *(*(p+1)+1) ==a[1][1]
        p指向了一个都是指针的数组(首地址),而数组元素的每个指针,指向了一个一维数组(首地址)
        p+1==a[1]==&a[1][0]
        *(p+1)==a[1]==&a[1][0] // 这个记忆方法放到下面可以理解
    
        // 下面三个方法是等价的
        printf("%d\n",*(a[1]+1)); //a[1]也是一个指针,表示一维数组
        printf("%d\n",*(*(p+1)+1));  // *(p+1) p+1表示一个一维数组,
        printf("%d\n",a[1][1]);
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    重要 - 第二种表达方法

    int a[2][3]={{1,2,3},{4,5,6}};
    
    int *p=&a[0][0]; // p=a p=&a[0] *p=a[0] a=&a[0] a[0]=&a[0][0]
    for (int i=0;i<2;i++)
    {
        for (int j=0;j<3;j++)
        {
            printf("%d,%d\n",a[i][j],*((p+i*3)+j));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    4. 字符型数组

    4.1 字符数组

    对于字符型数组,赋值个数小于开辟的内存空间,则初始化为"\0"printf("%c","\0")打印不出来任何东西
    字符型数组与数值数组的基本性质是一样的

    字符数组就是字符串
    c语言中没有字符串类型,所以借用字符数组存放一个字符串

    char str[10]={0} // 默认初始化为\0
    
    // 一维
    char s[10];
    
    // 多维
    char s[2][3];
    
    // 部分赋值
    char c[20]={' ','2'};
    
    // 全元素赋值,自动推导长度,这个属于字符数组,不属于字符串
    char d[]={'h','e',...};
    
    // 注意!!!
    char s[10];
    // s="hello world"; // 这种方式不行
    s[0]='0'; // 只能是下面这种方式
    s[1]='2';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    4.2 C语言字符串

    4.2.1 字符串创建/访问

    字符串 其实是字符数组
    字符数组就是字符串
    c语言中没有字符串类型,所以用 字符数组存放一个字符串
    可以通过索引获得元素字符
    在创建字符串的时候不能超出开辟的范围,不然会出错

    注意:下面创建的字符串,具有读写权限
    注意:如果复制直接"string"复制,而是逐个字符赋值给字符数组,要想创建字符串就要手动加上\0

    char s[30]={"hello world"}; // 等价于char s[30]={'h','e',...};
    
    // 其他常用方式
    char s[10]="hello world";
    
    char s[]={"hello world"};
    char s[]="hello world";
    
    // 为了避免\0的出现,最好的解决办法是
    char s[]={0}; // 进行默认初始化
    
    // 索引
    char a[10]="hello";
    printf("%c\n",a[1]);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    长度

    strlen计算不包括结尾的\0
    sizeof计算包含结尾的\0

    #include  // 必须要进行包含
    char a[]="hello";
    printf("%d\n",sizeof(a)); // 6
    printf("%d\n",strlen(a)); // 5
    
    // sizeof 要么等价于实际开辟的内存空间,要么要包含`\0`
    char a[10]="hello";
    printf("%d\n",sizeof(a)); // 10 , 注意这是要开辟的数组!!!
    printf("%d\n",strlen(a)); // 5
    
    
    // 对于数组越界 !!!
    char a[3]="hello";
    printf("%s\n",a); // 会发生未知字符
    printf("%d\n",sizeof(a)); // 3
    printf("%d\n",strlen(a)); //6 随机值,无法准确判断
    
    char a[6]="hello worlld";
    printf("%d\n",strlen(a)); // 9 是随机值,无法准确判断
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    第二种创建方式

    char s1[]="string1";
    char *s2="string2"
    
    // 初始化注意事项
    // char s1[10];
    // s1="hello"; // 这种不行
    char *s2;
    s2="world"; // 这种可以
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    char s1[]="string"

    这种方法定义的字符串所在的内存既有读取权限又有写入权限(可变),可以用于输入与输出函数
    []可以指明字符串的长度,如果不指明则根据字符串自动推算
    声明字符串的时候,如果没有初始化,(由于无法自动推算长度,只能手动指定)char s[10]

    char *s1="string"

    这种方法定义的字符串所在的内存只有读取权限,没有写入权限(不可变),只能用于输出函数

    对于 字符串修改
    https://blog.csdn.net/qq_31347869/article/details/105877116!!!
    也就是数组形式的字符串可以进行字符串修改(具有读写权限)
    指针形式的字符串不可以进行修改(只有读权限)

    注意:
    数组型字符串不可以进行互相赋值
    指针型字符串可以进行相互赋值

    // 下面是错的
    // char a1[10];
    // char a2[10]="hello"; // 这一个表达式是对的
    // char a3[10]=a2;
    
    // 下面是可以的
    char *a1="";
    char *a2="hello";
    printf("%s\n",a1);
    a1=a2;
    printf("%s\n",a1);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    字符串访问

    char arr1[]="hello world";
    char *arr2="hello cpp";
    
    for (int i=0;i<11;i++)
    {
        printf("%c,%c",arr1[i],*(arr1+i));
    }
    printf("\n");
    for (int i=0;i<9;i++)
    {
        printf("%c,%c\n",arr2[i],*(arr2+i));
    }
    
    printf("%s\n",arr1); // hello world
    printf("%s\n",arr2); // hello cpp
    printf("%s\n",arr1+1); // ello world
    printf("%s\n",arr2+2); // llo cpp
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.2.2 字符串细节

    4.2.2.1 \0

    \0不能显示,也没有控制功能
    字符串以\0结尾,注意字符数组不以\0结尾
    字符串逐个扫描,一旦遇到\0结束处理

    "string"会在字符串自动在末尾添加\0
    字符串的字符个数,要包括后面结尾的\0
    创建的时候,要为\0,多开辟一个内存空间,所以总数要比字符串+1

    // 创建字符串数组,下面这种方式只能逐个元素创建
    // 当创建的字符个数小于开辟的数量,剩下的为随机初始化的内容,有可能并不是\0,注意随机初始化和部分初始化的
    // 所以输出的可能字符个数可能大于30,因为printf直到\0的时候才会结束输出
    char s[30];
    char c;
    int i;
    for (c=65,i=0;c<=90;c++,i++){
    	s[i]=c;
    }
    printf("%s\n",s)
    
    // 手动添加\0
    // \0 的ASCII编码值是0
    // 下面两个等价
    s[i]=0; 
    s[i]='\0';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    4.2.3 字符串/地址/指针

    字符串与地址

    char a='c';
    printf("%p\n",&a);
    printf("%p\n",a); // 这两个地址不同,第二个写法应该取不出地址
    
    char b[10]="hello";
    printf("%p\n",&b); 
    printf("%p\n",b); // 字符串的名字是地址
    printf("%p\n",&b[0]); // 也是字符串第一个元素的地址
    
    int c[10]={1,2,3};
    printf("%p\n",&c);
    printf("%p\n",c); // 数组名也是地址
    printf("%p\n",&c[0]); // 也是数组第一个元素的地址
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    字符串与指针
    https://blog.csdn.net/L_fengzifei/article/details/126411708

    char* func()
    {
    	return "string"
    }
    
    • 1
    • 2
    • 3
    • 4

    对于字符串,整个引号中的内容作为指向该字符串存储位置的指针 !!!

    // are字符串的第一个字符的地址
    // 字符串可以当做指针*"family"相当于取指针指向的元素,取地址的元素,也就是该字符串的首元素
    printf("%s,%#p,%c\n","we","are",*"family"); // we,0x409001,f
    
    • 1
    • 2
    • 3

    遍历的一种写法

    void func(char *str)
    {
        while(*str) // '\0' 的ASCII 是0
        {
            *str=toUpper(*str); // 自己赋值给自己
            str++;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    字符串数组形式与指针形式的区别

    数组初始化是从静态存储区把一个字符串复制给数组
    指针初始化只是复制字符串的地址
    《c语言-函数 返回值与局部变量一节的例子》

    char arr1[]="hello"; // 栈区
    char *arr2="world"; // 常量区
    
    • 1
    • 2

    *char 字符串及其地址

    char *ptr=new char[10];
    printf("%#p\n",&ptr[0]); // 0x7d14d0
    printf("%#p\n",ptr); // 0x7d14d0
    printf("%#p\n",&ptr); // 0x61fe18
    printf("%#p\n",(char *)ptr); // 0x7d14d0
    printf("%#p\n",(void *)ptr); // 0x7d14d0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.2.4 字符串相关函数

    strcat/strcpy/strcmp
    // strcat 字符串拼接 // inplace操作
    char a1[20]="hello";
    char a2[10]="world";
    printf("%d\n",sizeof(a1)); // 20
    printf("%d\n",sizeof(a2)); //10 
    strcat(a1,a2);
    printf("%s\n",a1);
    printf("%d\n",sizeof(a1)); // 20 
    
    char a1[]="hello";
    char a2[]="world";
    printf("%d\n",sizeof(a1)); // 6
    printf("%d\n",sizeof(a2)); // 6
    strcat(a1,a2);
    printf("%s\n",a1);
    printf("%d\n",sizeof(a1)); // 6 ???
    
    // 没有写入权限,失败
    // char *a1="hello";
    // char *a2="world";
    // printf("%d\n",sizeof(a1)); // 20
    // printf("%d\n",sizeof(a2)); //10 
    // strcat(a1,a2); // 由于没有写入权限所以不行
    // printf("%s\n",a1);
    // printf("%d\n",sizeof(a1)); // 20 
    
    // strcpy 字符串复制 // inplace操作
    // 会将原来的数据全部进行覆盖
    // 值得注意的是: strcpy会把\0自动添加进去
    char a1[]="hellohello";
    char a2[]="world";
    printf("%d\n",sizeof(a1)); // 11
    printf("%d\n",sizeof(a2)); // 6
    strcpy(a1,a2); //world
    printf("%s\n",a1);
    printf("%d\n",sizeof(a1)); // 11 ???
    
    char a1[20]="hellohello";
    char a2[10]="world";
    printf("%d\n",sizeof(a1)); // 20
    printf("%d\n",sizeof(a2)); // 10
    strcpy(a1,a2); //world
    printf("%s\n",a1);
    printf("%d\n",sizeof(a1)); // 20
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    问题

    // 对于数组越界 !!!
    char a[3]="hello";
    printf("%s\n",a); // 会发生未知字符
    printf("%d\n",sizeof(a)); // 3
    printf("%d\n",strlen(a)); //6
    
    char name[5];
    strcpy(name,"zhangsan"); 
    printf("%s\n",name); // 不发生越界??? 有的编译器会报错warning
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    数值型/字符串转换

    把数字存储为字符串就是存储数字字符
    123 -> ‘1’ ‘2’ ‘3’ ‘\0’

    字符串转数值型
    • atoi

    字符串转整型

    // 下面两种字符串都可以
    char s[]="123";
    char *s="123";
    int var=atoi(s);
    printf("%d\n",var);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • atof

    字符串转整型

    // 下面两种字符串都可以
    // char s[]="123";
    char *s="123.456";
    double var=atof(s);
    printf("%f\n",var);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    数值型转字符串
    • itoa()
    #include
    int a=10;
    char s[10]={0};
    itoa(a,s);
    printf("%s\n",s)
    
    char a='a';
    char buffer[10];
    itoa(a,buffer,2); // 2表示转换基数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • sprintf
    // 整型转字符串
    int a=120;
    char str[20];
    memset(str,0,sizeof(str));
    sprintf(str,"%d",a);
    printf("%s\n",str);
    printf("%d\n",sizeof(str));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.3 字符串数组

    https://blog.csdn.net/cnds123321/article/details/122973636
    https://blog.csdn.net/u014157109/article/details/118718978

    第一种创建方式

    char strs[row][col] // row 表示有几个字符串,col每个字符串的最大长度
    char strs[2][10]={"hello","world"};
    
    // 下面这种是错误的
    // char strs[2][10];
    // strs={"hello","world"};
    
    // 上面相当于,因为对于一维来说这也是错误的,a是常量
    // char a[10];
    // a="hello";
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    第二种创建方式

    char* strs[row] // row 表示有几个字符串,每个字符串的类型是char* ;不能写成char (*strs)[row] 这相当于二维字符数组,每个字符数组有row个字符!!!
    
    char* strs[10]={"hello","world"};
    strs[0] 指向第一个字符串的首字符的地址,*strs[0]=='h'
    strs[1] 指向第二个字符串的首字符的地址,*strs[1]=='w'
    
    // 下面这种是错误的
    // char* strs[10];
    // strs={"hello","world"};
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    指针数组 – 还得再看

    数组中所有元素保存的都是指针,就叫做指针数组
    dataType *arr[]dataTye* (arr[])
    表明是一个数组,每个元素的类型都是dataType*
    注意 指针数组和二级数组的区分

    int* arr[3]={&a,&b,&c};
    int** p=arr; // 定义一个指向指针数组的指针,相当于(int*)* p=arr
    
    
    char* str[2]={"hello","world"};
    printf("%s\n",str[0]); // 输出"hello",char* s="hello";
    
    //下面的方法更好理解
    char* s1="hello";
    char* s2="world";
    char* s[2]={s1,s2};
    
    /*
    char *s="hello";
    
    char *s;
    s="hello";
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    6. 结构体数组

    数组中每个元素都是结构体

    struct stu{
    	char* name;
    	int num;
    }class[5];
    
    // 声明的时候初始化
    struct stu{
    	char* name;
    	int num;
    }class[5]={
    	{"li",1},
    	{"w",2}
    };
    
    // 声明的时候初始化,自动推断元素个数
    struct stu{
    	char* name;
    	int num;
    }class[]={
    	{"li",1},
    	{"w",2}
    };
    
    // 可以在结构体外,创建结构体数组
    struct stu class[]={
        {"li",2},
        {"wang",3}
    };
    
    // 访问数据
    class[0].name;
    class[1].num=2; //修改
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    6.1 结构体数组指针

    因为是数组,所以数组名可以作为元素的首地址

    struct stu class[]={
        {"li",2},
        {"wang",3}
    };
    
    // 下面两个是等价的,都表示地址
    printf("%p\n",class);
    printf("%p\n",&class[0]);
    
    // 所以可以利用指针指向地址
    struct stu* pstu=class; 
    
    // 下面这两种访问方法都是等价的
    struct stu* pstu=class; // 看成普通数组就行
    for (int i=0;i<2;i++){
        printf("%s,%d\n",(pstu+i)->num,(pstu+i)->age);
    }
    
    for (int i=0;i<2;i++,pstu++){
        printf("%s,%d\n",pstu->num,pstu->age);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    6.2 结构体数组/指针 进阶

    结构体名 不是该结构体的起始地址,更不是该结构体首元素的地址
    结构体的起始地址,在值上 等于 该结构体首元素的地址

    #include 
    
    #define LEN 10
    
    using namespace std;
    
    struct names
    {
        char first[LEN];
        char last[LEN];
    };
    
    struct people
    {
        struct names handle;
        char favfool[LEN];
        char job[LEN];
        float income;
    };
    
    
    int main()
    {
        // 结构体名 不是该结构体的起始地址
        // 结构体的起始地址,在值上 等于 该结构体首元素的地址
        struct names name1={"li","wang"};
        // std::cout<
        printf("%p\n",name1); // 0x61fd40 不等于首元素的地址
        printf("%p\n",&name1); // 0x61fdf0 相当于整个结构体的起始地址 而结构体的起始地址 的值 等于第一个成员的地址
        printf("%p\n",name1.first); // 0x61fdf0 
        printf("%p\n",&name1.first); // 0x61fdf0 该成员(整个成员)的起始地址
        printf("%p\n",&name1.first[0]); // 0x61fdf0 等价于 name1.first
    
        struct people people1={
            {"li","wang"},
            "apple",
            "ex",
            20.1
        };
    
        printf("%s\n",people1.handle.first); 
        printf("%s\n",people1.handle.last); 
        printf("%p\n",people1); // 0x61fd10 !!! 说明结构体名不等于首元素的地址也不等于结构体的起始地址
        printf("%p\n",&people1); // 0x61fdc0 整个外层结构体的起始地址
        printf("%p\n",people1.handle); // 0x61fd10 !!! 说明结构体名不等于首元素的地址也不等于结构体的起始地址
        printf("%p\n",&people1.handle); // 0x61fdc0 整个内存结构体的起始地址 等于 整个外层结构体的起始地址
        printf("%p\n",people1.handle.first); // 0x61fdc0 下面三个是数组的地址
        printf("%p\n",&people1.handle.first); // 0x61fdc0 
        printf("%p\n",&people1.handle.first[0]); // 0x61fdc0 
      
        struct people peoples[2]={
            {
                {"li","wang"},
                "apple",
                "ex",
                20.1
            },
            {
                {"zhang","san"},
                "pea",
                "ex2",
                22.1
            },
        };
    
        // 结构体指针
        struct people *ptr1;
        ptr1=&people1; // 结构体的名字并不等于结构体元素的首地址!!! 指向起始地址是对的,因为类型是匹配的
        cout<<ptr1->favfool<<","<<ptr1->job<<","<<ptr1->income<<endl;
    
        // 结构体数组指针 把他想象成普通数组的指针就行了
        struct people * ptr;
        // ac - 84 = int 44 一共44个字节
        cout<<&peoples[0]<<" "<<&peoples[1]<<endl; // 0x61fd80 0x61fdac 这个相等于起始地址
        cout<<&peoples<<endl; // 0x61fd80 整个数组的地址
        cout<<peoples<<endl; // 数组首元素的地址,也就是第一个结构体的地址
        // 结构体的名字并不等于结构体元素的首地址!!! 
        
        // version1
        ptr=peoples;
        cout<<ptr<<" "<<ptr+1<<endl; // 0x61fd80 0x61fdac
        cout<<ptr->job<<endl;
        
        // version2
        ptr=&peoples[0]; // struct people 所以+1 跨过了整个数组
        cout<<ptr<<" "<<ptr+1<<endl; // 0x61fd80 0x61fdac ptr+1跨过了整个结构体数组中的一个元素
        cout<<ptr->income<<endl;
    
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
  • 相关阅读:
    【机器学习】特征选择之包裹式特征选择法
    创建线程池执行java代码逻辑
    [工业互联-6]:PLC工业控制系统快速概览
    华为OD机试 - 代表团坐车 - 动态规划(Java 2023 B卷 200分)
    云计算-Linux-查看内核-CPU,内存,网卡,主机名信息,修改主机名
    【狂神说Java】Mybatis-plus
    WRF学习笔记之四:撰写WPS intermediate file添加海冰场/NCL学习/WRF进阶:如何向WRF添加额外气象场?
    C++智能指针
    【Pandas总结】第八节 Pandas 合并数据集_pd.merge()
    Python爬虫实战:抓取和分析新闻数据与舆情分析
  • 原文地址:https://blog.csdn.net/L_fengzifei/article/details/127921665