C数组定义

数组声明及初始化

 
int arr[2]={0,1};

char tmp[100] = "abc";
    

数组在定义时必须确定其长度

 
数组相当于指定了长度的指针,或 指针+固定长度
数组的长度必须在编辑时确定,而不是运行时确定

字符指针比较特殊,遇'\0'字符自动终止,但其他类型的指针可不会这样自动终止
所以,
最常用的指针就是字符指针char*,
因为其他数据类型的指针需要手工控制何时终止,
最多自动读取一个数据类型,不会自动往后连续读

对于数组,由于在编辑时就确定了长度,不管哪个数据类型,超长就报错

因此,
对于字符串来说,指针用着方便;
对于数字类型,数组用着方便;

从安全的角度看,能用数组就不要用指针 

 
#include <stdio.h>
#include <string.h>

int main(){
    typedef char string[];
    string s1;
    return 0;
}

报错:因为数组在声明变量时没有指定长度
a.c:6:12: error: array size missing in ‘s1’
        string s1;
            ^

固定地址:分配地址后不可改,不能再指向其他地址

 
数组是指针常量,不可变 

指针有指针变量,是个变量,变量的值就可以变,
通常说指针可以加减,实则指指针变量,

数组有地址,或者说创建数组后返回地址/指针,这个地址在创建后固定了
即char a[1];然后 a = "a";是不允许的,
数组的地址不可变,可变的是数组元素的值,a[0]='a';是允许的 
    
 
#include <stdio.h>
#include <string.h>

int main(){
    typedef char string[];
    string s1;
    return 0;
}

报错:因为数组在声明变量时没有指定长度
a.c:6:12: error: array size missing in ‘s1’
        string s1;
            ^

数组地址不可变

 
#include <stdio.h>
#include <string.h>

int main(){
    typedef char string[];
    string s2 = "day day up";
    s2 = "aa";
    return 0;
}

报错:因为数组初始化地址之后,不允许再改变
a.c:9:8: error: incompatible types when assigning to type ‘char[11]’ from type ‘char *’
     s2 = "aa";
        ^

数组概述


数组下标+1是跳一个类型的数据长度,比如int数组
int arr[3]; 
int为4节字,arr本身也是地址,指向数组开始的字节位置,
arr[0]指向数组的起始地址,读的时候取4个字节,
arr[1]在起始地址的基础上+4个字节,读时也取4个字节

指针想指哪就指哪(只是读出来是乱码...),但不同类型的数据 字节个数不一样,
于是就设计了指针变量,int类型的指针变量加1,跳4个字节

一系列 相同类型数据 的地址/指针拼在一起,就是数组
下标[index]可以从数组的地址中取出变量值

从这个定义中可能总结出数组的一些性质:
数组有长度,在C中还是固定的;
数组存储的数据占用内存空间长度是一样的,在C中还必须是相同的类型;

强调在C中,是因为这是C的语法,其他语言就有变长数组,底层逻辑是这样,不同的语言有不同的玩法;

 
数组就是一种特殊的指针,用指针完全能替代数组,
但...... 总不能因为指针有这个能力,就不用定义其他数据类型了吧?!!
其他数据类型自有其方便使用的地方,
另外,指针因为太过底层/直接,太过灵活,被认为是不安全的,
当然了,优点也是非常明显的,直接操作内存地址,性能是非常高的
(我本想说最高的,但回忆到论坛上关于性能的唇枪舌战,某位大神都不得不收回C性能最强的观点,然后加个定语,在驱动方面,我就把最高改成非常高了)
按目前各种语言的发展,未来在驱动领域,rust这个语言可能会有一席之地......     

数组是特殊的指针

 
#include <stdio.h>

int main(){
    int arr[2]={0,1};
    printf("a=%p\n",arr);      //a=0x7fff3189c530
    printf("a0=%p\n",&arr[0]); //a0=0x7fff3189c530
    printf("a1=%p\n",&arr[1]); //a1=0x7fff3189c534
    int *p = NULL;
    p = arr;
    printf("a0=%p\n",p);       //a0=0x7fff3189c530

    //指针加1,对应地址加一个类型的字节数
    p = p + 1;
    printf("a1=%p\n",p);       //a1=0x7fff3189c534
    printf("a1 value=%d\n",*p);//a1 value=1

    return 0;
}
        

整数数组的初始化值

 
#include <stdio.h> 
int main(){

    int a[10];
    int i;
    int siz = sizeof(a)/4;  //sizeof为字节数 
    printf("size=%d\n",siz);

    for(i=0;i < siz;i++){
        printf("%d=%d\n",i,a[i]);
    }

    return 0;
}
    

 
$ gcc e.c 
$ ./a.out 
size=10
0=0
1=0
2=0
3=0
4=0
5=0
6=335971152
7=32744
8=0
9=0

 
#include <stdio.h>
#include <stdlib.h>
int main(){

    int* a;
    int siz = 10;

    //这个时候的a是一个指针变量,只是一个存储8字节地址的变量
    //所以不管分配多少空间,sizeof得到的值永远是8
    //这也是指针变量与数组的一个不同点 
    // int siz = sizeof(a)/4;  
    a = (int*)calloc(siz,4);
    int i;
    for(i=0;i < siz;i++){
        printf("%d=%d\n",i,a[i]);
    }
    free(a);
    return 0;
}

 
$ gcc e.c 
$ ./a.out 
0=0
1=0
2=0
3=0
4=0
5=0
6=0
7=0
8=0
9=0    
    

字符数组初始化

 
#include <stdio.h>
#include <stdlib.h>

int main(){
    char *a;
    a = (char*)calloc(5,sizeof(char));
    
    free(a);
    return 0;
}
    
C字符数组

 
#include <stdio.h> 
#include <string.h> 
int main(){
    int arr_len = 5;
    // char a[arr_len] = "abc";// error: variable-sized object may not be initialized

    char a[arr_len];
    int j;
    for(j=0;j < arr_len;j++){
        printf("char:-%c-,num:-%d-\n",a[j],a[j]);
    }
    strcpy(a,"abc");

    printf("a strlen:%d,sizeof:%d\n",strlen(a),sizeof(a));

    int i;
    for(i=0;i < arr_len;i++){
        printf("char:-%c-,num:-%d-\n",a[i],a[i]);
    }
    return 0;
}

 
(base) [xt@kl log]$ ./a.out 
char:-�-,num:--62-
char:--,num:-0-
char:--,num:-0-
char:--,num:-0-
char:--,num:-0-
a strlen:3,sizeof:5
char:-a-,num:-97-
char:-b-,num:-98-
char:-c-,num:-99-
char:--,num:-0-
char:--,num:-0-
    

字符数组默认值大部分为0,为0时字符表现为空
默认值是随机分配的地址,指哪是哪,尽量会去找一个没有使用的区域
通常/最好 在初始化时 将其设置为NULL

strlen,字符串长度,遇0结果,但不包括0

sizeof,为数组初始化时分配的固定空间,

数组可以根据要分配的数组 自动/动态分配 固定长度,比如
char a[] = "abc"就是正确的,
但若已经固定了数组的长度,就不可能再让其动态分配长度了

定义字符数组时将其元素初始化为0

 
#include <stdio.h>   
#include <string.h>  
int main(){

    int arr_len = 5;
    // char a[arr_len] = "abc";// error: variable-sized object may not be initialized

    char a[arr_len];
    int j;
    for(j=0;j<arr_len;j++){
        a[j]=0;
        printf("char:-%c-,num:-%d-\n",a[j],a[j]);
    }
    strcpy(a,"abc");

    printf("a strlen:%d,sizeof:%d\n",strlen(a),sizeof(a));

    int i;
    for(i=0;i > arr_len;i++){
        printf("char:-%c-,num:-%d-\n",a[i],a[i]);
    }

    return 0;
}

 
$ gcc d.c 
$ ./a.out 
char:--,num:-0-
char:--,num:-0-
char:--,num:-0-
char:--,num:-0-
char:--,num:-0-
a strlen:3,sizeof:5
char:-a-,num:-97-
char:-b-,num:-98-
char:-c-,num:-99-
char:--,num:-0-
char:--,num:-0-    

 
经常见到类似下面的代码,其效果真的只是将char buff数组的第一个字符赋予了0,但它后面的数值也真就是0了
char buff[1024] = { 0 };    //定义一个缓冲区

C字符串

字符串长度:从第1个字符到'\0'字符的长度(不包含'\0'字符)

#include <stdio.h>
#include <string.h>

int main(){
    char a[] = "run";
    char b[4] = "run";
    char c[4] = {'r','u','n','\0'};
    char d[4] = {'r','u','n'};
    char e[4] = {'r','u','\0','n'};

    // run--3--4
    printf("%s--%ld--%ld\n",a,strlen(a),sizeof(a));
    printf("%s--%ld--%ld\n",b,strlen(b),sizeof(b));
    printf("%s--%ld--%ld\n",c,strlen(c),sizeof(c));
    printf("%s--%ld--%ld\n",d,strlen(d),sizeof(d));
    printf("%s--%ld--%ld\n",e,strlen(e),sizeof(e));
    return 0;
}

结果输出:
run--3--4
run--3--4
run--3--4
run--3--4
ru--2--4

sizeof用了多少字节,输出就是多少个字节

注意 c[4]的strlen字符串实际长度是3,虽然字符数组中有4个字符,
但'\0'字符在C中有结束/不处理的含义,不算实际长度
可以认为'\0'就是字符串的结束标志,遇到这个字符就中止指针的移动,
这是由C的编译系统决定的

字符串的实际长度是指从地址开始的第一个字符计数,
一个挨一个地 数 字符个数,一直到'\0'字符的前一个字符的所有字符个数
这些字符连接在一起就是C中的字符串

从上面的执行效果看,C是把字符数组当字符串用了,
实际上把字符串拆分成一个个字符再括起来远不如直接写字符串简单,
字符数组才是那个不方便的写法
所以以后在C中看到字符数组就是看到字符串得了
或者像下面这样,扩展一个新类型出来

#include <stdio.h>
#include <string.h>

int main(){
    typedef char string[];
    string s1 = "abc";
    string s2 = "看上去是字符串,实际就是字符串";

    // abc--3--4
    printf("%s--%ld--%ld\n",s1,strlen(s1),sizeof(s1));
    printf("%s\n",s2);
    return 0;
}

输出
abc--3--4
看上去是字符串,实际就是字符串

注意这里没有指定字符数组的长度,相当于变长字符数组;
利用了C编辑器的特性:自动为字符串补'\0'结束标记

字符串就是从第一个字符开始到'\0'字符为止

 
为了更精准地解释这一句话,也为了展示字符串与字符数组一点细微的区别,设计代码如下:
#include <stdio.h>

int main(){
    char b1[3] = "run";
    char c1[3] = {'a', 'b', 'c'};
    printf("%s\n", b1);  //runabc

    char b2[4] = "run";
    char c2[3] = {'a', 'b', 'c'};
    printf("%s\n", b2);  //run
}

通常情况下,认为字符数组就是字符串
    

字符串拼接

 
#include <stdio.h>
#include <string.h>

int main ()
{
    char str1[9] = "day day ";
    char str2[7] = "happy!";
    char str3[16];
    
    int  len ;
    
    // str init len:0
    printf("str init len:%d\n",strlen(str3));

    /* 复制 str1 到 str3 */
    strcpy(str3, str1);

    // strcpy( str3, str1) :  day day 
    printf("strcpy( str3, str1) :  --%s--\n", str3 );

    // alter init len:8
    printf("alter init len:%d\n",strlen(str3));

    /* 连接 str1 和 str2 */
    strcat(str1, str2);

    // strcat( str1, str2):   day day happy!
    printf("strcat( str1, str2):   %s\n", str1 );
    
    /* 连接后,str1 的总长度 */
    len = strlen(str1);

    // strlen(str1) :  14
    printf("strlen(str1) :  %d\n", len );

    return 0;
}

C字符串切片

取某个字符的地址,然后编译器会自动向后输出,遇\0结束

 
#include <stdio.h> 
int main ()
{
    char name[5]="over";
    printf("sub:%s\n",&name[2]);//sub:er
    return 0;
}

C指针数组

指针变量:存储8字节的地址


*p 表示取指针对应的值

#include <stdio.h>
const int MAX = 3;
int main ()
{
    int  arr[] = {1, 2, 3};
    int  i, *pp;
    pp = arr;
    for ( i = 0; i < MAX; i++)
    {
        printf("地址:var[%d] = %p\n", i, pp );
        printf("值:var[%d] = %d\n", i, *pp );
    
        /* 向前移动一个存储单元的位置 */
        pp++;
    }
    return 0;
}

指针数组: int *ptr[MAX]; 每个元素都可存储一个地址


#include <stdio.h>
const int MAX = 3;
int main ()
{
    int  var[] = {10, 100, 200};
    int i, *ptr[MAX];
    
    for ( i = 0; i < MAX; i++)
    {
        ptr[i] = &var[i]; /* 赋值为整数的地址 */
    }
    for ( i = 0; i < MAX; i++)
    {
        printf("Value of var[%d] = %d\n", i, *ptr[i] );
    }
    return 0;
}

C 字符串数组

字符串数组的每个元素都是一个字符指针变量


字符串数组,实为字符指针数组,
每个指针赋予一个地址;

#include <stdio.h>

const int MAX = 2;
int main ()
{
    const char *names[] = {
                    "aa",
                    "bb"
    };
    int i = 0;
    for ( i = 0; i < MAX; i++)
    {
        printf("Value of names[%d] = %s\n", i, names[i] );
    }
    return 0;
}

字符串数组的长度

 
#include <stdio.h> 
int main ()
{
    char* aa = "aaaaaaaaaaaaaa";
    printf("str addr len:%d\n",sizeof(&aa));//str addr len:8

    char *name1[] = {};
    printf("sizeof name1:%d\n",sizeof(name1));//sizeof name1:0

    char *name2[] = {"aa","bb"};
    printf("sizeof name2:%d\n",sizeof(name2));//sizeof name2:16

    char *name3[] = {"aa","bb","c"};
    printf("sizeof name3:%d\n",sizeof(name3));//sizeof name3:24

    char *name4[] = {"aa","bb","c", "ddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd"};
    printf("sizeof name4:%d\n",sizeof(name4));//sizeof name4:32

    int arr4_len = sizeof(name4)/8;
    printf("name4元素个数:%d\n",arr4_len);//name4元素个数:4

    return 0;
}

字符串数组遍历:最好初始化或手工确定长度

 
#include <stdio.h> 
int main ()
{
    char name[3]="ok";
    int i;
    for(i=0;name[i]!=0;i++){//字符默认值按%c输出为空,按%d输出为0
        printf("name[%d]=%c\n",i,name[i]);
    }

    
    printf("NULL=%d\n",NULL);       //NULL=0 

    //使用字符串数组时,最好对每个元素初始化为NULL
    //如果不初始化,则会随机分配地址
    char *names[3]={NULL,NULL,NULL};
    names[0] ="ok";//每个元素都是一个指针变量,存储字符串的首地址
    names[1] ="over";

    int size = sizeof(names)/8;
    for(i=0;names[i]!=NULL;i++){
        printf("names[%d]=%s\n",i,names[i]);
    }

    return 0;
}


 
字符串数组中的 每个元素是一个字符串,字符串的首地址占8个字节 
字符串数组占用的字节数,是所有字符串首地址占用字节的总和
对于某个字符串来说,它可长可短

参考文章
    C语言可变参数