C语言进阶(函数+指针+数组)

   上一篇简单说了一下数组的基本概念以及在内存中的应用,这一篇将详细解析数组和指针数组还有函数指针数组这三者的关系

一丶 数组的进阶

  1.  数组名的含义

  • 在C语言程序中,数组的出现又两种可能含义:
    • 代表整个数组
    • 代表其首元素的地址
  • 当出现以下情形时,数组代表的是整个数组
    • 在数组定义中
    • 在sizeof运算符表达式中
    • 在取地址符
  • 当出现其他情形时,数组代表其首元素的地址
    • 指针指向该数组首元素的地址时
    • 函数传参时
    • 使用scanf函数
    • 直接使用数组名
    • 取址的时候&a,用sizeof求数组大小的时候sizeof(a) = 16,数组名代表整个数组
#include <stdio.h>

// 主函数
int main(int argc, char const *argv[])
{
    // (1)、- 当出现以下情形时,数组代表的是整个数组
    // 1、- 在数组定义中
    int buf1[5] = {0};  // 将整个数组里面的所有元素全部初始化为零,
                        // 数组在内存中不能随便移动位置(数组名不能进行加减操作:buf1 = buf1+1)(数组名相当于一个常量指针)  
    /*
        解析:
            buf1 = buf1 +1;
        出现的问题:
            error: assignment to expression with array type
            // 翻译:错误,赋值的是一个数组类型的表达式
    */

    // 2、- 在sizeof运算符表达式中
    printf("数组buf1的大小为 == %lu个字节\n", sizeof(buf1));
    /*
        打印出来的数据为:
            数组buf1的大小为 == 20个字节
        解析:
            sizeof是将其当成一个指针来对待吗??
            如果将其当成了一个指针来对待的话,那么sizeof(buf1)的大小就应该为系统位数的大小
            所以,sizeof(buf1)其实是算这个buf1数组的大小,而非指针的大小
    */

    // 3、- 在取地址符(看其作用域即可知道其作用范围)
    // a、代表数组buf1的首元素的地址
    printf("数组buf1的首元素的地址    == %p\n", buf1);
    printf("数组buf1的首元素的地址+1  == %p\n", buf1+1);
    /*
        解析:
            数组buf1的首元素的地址    == 0x7ffeb38623c0
            数组buf1的首元素的地址+1  == 0x7ffeb38623c4
        说明:
            buf1和buf1+1的地址相差4个地址(4个字节),所以能够说明
            buf1代表的是首元素的地址
        
    */

    // b、代表整个数组buf1的地址
    printf("整个数组buf1的地址   == %p\n", &buf1);
    printf("整个数组buf1的地址+1 == %p\n", &buf1+1);
    /*
        解析:
            整个数组buf1的地址   == 0x7ffc24b773c0
            整个数组buf1的地址+1 == 0x7ffc24b773d4
        说明:
            &buf1和&buf1+1的地址相差20个地址(20个字节),而数组buf1的大小正好是20个字节,所以能够说明
            &buf1代表的是整个数组buf1的地址(其作用范围是20个字节)
    */

    // (2)、- 当出现其他情形时,数组代表其首元素的地址
    // 1、- 指针指向该数组首元素的地址时
    int buf2[128] = {0};
    int *p1       = buf2;
    int *p2       = &buf2[0];

    printf("数组buf2的首元素的地址    == %p\n", buf2);
    printf("数组buf2的首元素的地址+1  == %p\n", buf2+1);

    printf("p1的变量里面存放的地址    == %p\n", p1);
    printf("p1+1的变量里面存放的地址  == %p\n", p1+1);

    printf("p2的变量里面存放的地址    == %p\n", p2);
    printf("p2+1的变量里面存放的地址  == %p\n", p2+1);
    /*
        解析:
            数组buf2的首元素的地址    == 0x7fff58faf870
            数组buf2的首元素的地址+1  == 0x7fff58faf874

            p1的变量里面存放的地址    == 0x7fff58faf870
            p1+1的变量里面存放的地址  == 0x7fff58faf874

            p2的变量里面存放的地址    == 0x7fff58faf870
            p2+1的变量里面存放的地址  == 0x7fff58faf874
        
        说明:
            指针p1和p2的移动地址和范围和buf2一样,所以证明指针指向该数组地址时,指向的是其数组首元素的地址
    
    */

    // 2、- 函数传参时
    char str[] = "abcdefghijklnmopqrstuvwxyz";
    printf("原数组:%s\n", str);
    upper_case(str, CAL_ARR_NUM(str));
    printf("转换后:%s\n", str);

    // 3、- 使用scanf函数
    char buf3[128] = {0};
    printf("请输入你想要输入的数据:\n");    
    // scanf("%s", &buf3);      // buf3表示数组的首元素的地址(&buf3代表的是整个数组的地址,切记不要写&符号)
    printf("buf3 == %s\n",buf3);
    /*
        解析:
            : warning: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char (*)[128]’ [-Wformat=]
            翻译:要求是char *类型,但是实际类型是char(*)[128]类型
        说明:
            所以在使用scanf函数的使用,数组buf3前面不用家&符号,因为类型不匹配

    */
    int a = 10; //在内存中申请了一段4个字节的连续的存储空间,用变量名a来访问这段内存空间
    //数据占用内存空间的大小
    printf("sizeof(a) = %ld\n",sizeof(a));
    //用地址来描述这段内存空间
    printf("&a = %p\n",&a); //16进制表示


    return 0;
}

2.  字符串数组 

   字符串常量在内存中的存储,实质就是一个匿名数组

   匿名数组,同样满足数组两种涵义的规定

   格式:字符串常量后面默认带有 一个'\0'的结束符

        char s[] = "hello"; //常用的字符串写法(字符数组)strlen=5 sizeof=6

        char s1[] = {'h','e','l','l','o','\0'}; //表示的是字符串 strlen=5 sizeof=6

        char s2[] = {"hello"}; //可以省略大括号

        char s3[] = {'h','e','l','l','o'}; //表示的是字符序列 它不是字符串 strlen=无意义 sizeof=5

/*
        字符数组的特别之处
        #include <string.h>
        size_t strlen(const char *s); //strlen里面必须填字符串的数据
        字符串的两种表示新式:char *str  char str[5]
        然后char *str 和 char str[5]是等价的

        说明:
        数字0 和 字符'0'不是等价的
    */
    char arr3[4] = {0};
    printf("strlen(arr3) = %ld\n",strlen(arr3)); //0个有效字节

    char arr4[4] = {'0','1','2'}; //字符数组的写法,但不是字符串的描述
    printf("strlen(arr4) = %ld\n",strlen(arr4)); //3个有效字节

    char arr5[4] = {'0','1','2','\0'}; //字符数组的写法,这个是字符串的描述(因为字符串末尾必须带一个'\0')
    printf("strlen(arr5) = %ld\n",strlen(arr5)); //3个有效字节

    char arr6[4] = "012"; //字符串"012" 和上面的arr5是等价的写法但是和arr4不是等价的写法
    printf("strlen(arr6) = %ld\n",strlen(arr6)); //3个有效字节

    //printf("strlen(arr1) = %ld\n",strlen(arr1)); //错误类型不匹配 真实的原因:和指针有关后面再讨论

3.  特殊数组

零长数组

        概念:长度为0的数组,比如int data[0] // 相当于一个地址入口(很像指针),但是没有内存空间

        用途:放在结构体的末尾,作为可变长度数组的入口

不定长数组

        概念:数组buf[N]的长度不由里面的N决定,而由其初始化列表的元素个数决定

        用途:不确定要赋值的数据的长度,又不想浪费内存空间,即可使用

变长数组(变量 --- 数组的长度由变量决定)

        概念:定义时,使用变量作为元素个数的数组

        要点:变长数组仅仅指元素个数在定义时是变量,而绝非指数组的长度可长可短,实际上,不管是普通数组还是变长数组,数组一旦定义完毕,其长度则不可改变

4.  二维数组以及与地址的关系

        这块逻辑在上一篇博客有详细介绍 

       博客链接:  C语言中的难点--地址与数组之间的关系解析-CSDN博客

5.  示例代码

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


//数组+函数
//数组传参的时候:数组名+数组元素个数
//参数:数组名+数组元素个数 
void func(int arr[],int len)
{
    int i = 0;
    for(i = 0; i < len; i++)
         printf("%d ",arr[i]);
    printf("\n");
}

void func1(int arr[4])
{
    int i= 0;
    for(i = 0; i < 4; i++)
        printf("%d ",arr[i]);
    printf("\n");
}

int main(int argc, char const *argv[])
{
    /*

        int a[10] = {0};
        int i = 0;
        srand(time(0));   // 初始化随机种子

        for(i = 0; i < 10; i++)
        {
            a[i] = rand()%100;    //随机输出100以内的数赋值给a[i]
            printf("%d ",a[i]);
        }

        //数组的越界访问 如果赋值超出合法范围 会有随时被篡改的风险

        //数组的赋值
        char arr[5]= {0}; //还没有想好赋值的时候会给覆盖掉

        //定义+初始化
        char arr1[5] = {'a','b','c','d','e'};//字符数组
        short arr2[5] = {1,2,3,4,5,};
        float arr3[5] = {1.1,2.2,3.3,4.4,5.5};

        //先定义再初始化
        int arr4[5] = {0};
        arr4[0] = 1; //一般结合for循环赋值

        //非常规赋值方法
        int arr5[5] = {0}; //五个数都是0
        int arr6[6] = {1,2}; //前两个数赋值后面的数默认赋值为0
        int arr7[5] = {1,2,3,4,5,6,7}; //error 数的个数超过了范围
        int arr8[5] = 0;      //error
        //数组的大小由你赋值的个数来决定
        int arr9[] = {1,2,3,4,5};

        // int arr10[]; //数组的大小迷失了  arr size missing

    */

    // strlen()和sizieof()的区别
    // 计算数组的长度(只能计算字符数组:strlen)和大小sizeof
    char arr11[] = "hello world";
    int len[5] = {1, 2, 3, 4, 5};
    printf("sizeof(arr11) = %d\n", sizeof(arr11));
    printf("strlen(arr11) = %d\n", strlen(arr11));
    // printf("strlen(len) == %ld\n",strlen(len));//errro strlen不能计算证书数组长度

    int a = 100;
    //&:取址符(地址的大小是8个字节)随机值
    printf("&a:%p\n", &a); //&a:0x7ffe47489ea4

    char c = 'a';
    printf("&c:%p\n", &c); // 如何去描述一个数据在计算机中的内存

    int a1[3] = {0};
    printf("&a1;%p\n", &a1); //取的是存放整个数组的地址
    // 这个地址和上面的地址描述的含义不一样
    //  printf("a1;%p\n",a1);//这个是取地址数组a[0](等价于&a[0])的地址

    // 打印数组的长度(只能打印字符数组(字符串)的长度)、大小和、地址
    // char arr[4]
    // short arr1[4]
    // int arr2[4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值