C语言指针与数组深入剖析及优化示例 指针解读 数组与指针的关系

说明:

这是个人对该在Linux平台上的C语言学习网站笨办法学C上的每一个练习章节附加题的解析和回答

ex14:

  • 重新编写这些函数,使它们的数量减少。比如,你真的需要can_print_it吗?

    cs 复制代码
    if(isalpha(ch) || isblank(ch)) {
        printf("'%c' == %d ", ch, ch);
    }
  • 使用strlen函数,让print_arguments知道每个字符串参数都有多长,之后将长度传入print_letters。然后重写print_letters,让它只处理固定的长度,不按照'\0'终止符。你需要#include <string.h>来实现它。

    cs 复制代码
    #include <stdio.h>
    #include <ctype.h>
    #include <string.h>
    
    // forward declarations
    int can_print_it(char ch);
    void print_letters(char arg[],char str_len);
    
    void print_arguments(int argc, char *argv[])
    {
        int i = 0;
    
        for(i = 0; i < argc; i++) {
            print_letters(argv[i],strlen(argv[i]));
        }
    }
    
    void print_letters(char arg[],char str_len)
    {
        int i = 0;
    
        for(i = 0; i < str_len; i++) {
            char ch = arg[i];
    
            if(isalpha(ch) || isblank(ch)) {
                printf("'%c' == %d ", ch, ch);
            }
        }
    
        printf("\n");
    }
    
    int main(int argc, char *argv[])
    {
        print_arguments(argc, argv);
        return 0;
    }
  • 使用man来查询isalphaisblank的信息。使用其它相似的函数来只打印出数字或者其它字符。

    其他类似函数:
    isdigit: 检查字符是否为数字(0-9)。
    ispunct: 检查字符是否为标点符号。
    isxdigit: 检查字符是否为十六进制数字。
    isalnum: 检查字符是否为字母或数字。
    iscntrl: 检查字符是否为控制字符。

    cs 复制代码
    #include <stdio.h>
    #include <ctype.h>
    #include <string.h>
    
    // Function prototypes
    void print_arguments(int argc, char *argv[]);
    void print_letters(const char *arg);
    void print_digits(const char *arg);
    void print_punctuation(const char *arg);
    
    void print_arguments(int argc, char *argv[]) {
        for (int i = 0; i < argc; i++) {
            printf("Argument %d: %s\n", i, argv[i]);
            printf("Letters: ");
            print_letters(argv[i]);
            printf("Digits: ");
            print_digits(argv[i]);
            printf("Punctuation: ");
            print_punctuation(argv[i]);
            printf("\n");
        }
    }
    
    void print_letters(const char *arg) {
        for (int i = 0; arg[i] != '\0'; i++) {
            char ch = arg[i];
            if (isalpha(ch) || isblank(ch)) {
                printf("'%c' == %d ", ch, ch);
            }
        }
        printf("\n");
    }
    
    void print_digits(const char *arg) {
        for (int i = 0; arg[i] != '\0'; i++) {
            char ch = arg[i];
            if (isdigit(ch)) {
                printf("'%c' == %d ", ch, ch);
            }
        }
        printf("\n");
    }
    
    void print_punctuation(const char *arg) {
        for (int i = 0; arg[i] != '\0'; i++) {
            char ch = arg[i];
            if (ispunct(ch)) {
                printf("'%c' == %d ", ch, ch);
            }
        }
        printf("\n");
    }
    
    int main(int argc, char *argv[]) {
        print_arguments(argc, argv);
        return 0;
    }
  • 上网浏览不同的人喜欢什么样的函数格式。永远不要使用"K&R"语法,因为它过时了,而且容易使人混乱,但是当你碰到一些人使用这种格式时,要理解代码做了什么。

    旧的"K&R"函数定义形式(Kernighan and Ritchie风格),来源于C语言的初始版本,在ANSI C(1989年)现代风格之前非常流行。这种形式在现代C编程中已经过时,但仍然可以在一些老旧的代码中看到。它的特点是函数参数的类型声明放在函数体之前,单独列出 ,而不是在函数头部直接定义参数及其类型。

    cs 复制代码
    int sum(a, b)
    int a;  // 参数类型声明
    int b;  // 参数类型声明
    {
        return a + b;
    }

    ex15:

  • 一些指针的疑问总结:

    指针的类型和不同级数的异同点?
    所有的指针(无论几级指针都是一个存储指针地址的变量,都是只有操作系统的位数来决定的,要么是64位要么是32位),而指针的类型(例如:int * ,char *)决定了指针每次读取数据的内存地址偏移量
    例子:

    cs 复制代码
    #include <stdio.h>
    
    int main() {
        char arr_char[] = "Hello"; 
        int arr_int[] = {10, 20, 30, 40};  
    
        char *p_char = arr_char;
        int *p_int = arr_int;
    
        // 输出原始地址
        printf("Original char pointer address: %p\n", p_char);
        printf("Original int pointer address: %p\n", p_int);
    
        // 输出加 1 后的地址
        printf("char pointer after p_char + 1: %p\n", p_char + 1);//偏移量1
        printf("int pointer after p_int + 1: %p\n", p_int + 1);//偏移量4
    
        return 0;
    }
    /*输出
    Original char pointer address: 0x7ffeeef25a50
    Original int pointer address: 0x7ffeeef25a60
    char pointer after p_char + 1: 0x7ffeeef25a51
    int pointer after p_int + 1: 0x7ffeeef25a64
    */

    有关指针*& 操作符解释:
    * 是解引用操作符,用于访问指针指向的值。解引用一个一级指针时,访问的是指针指向的数据;解引用一个二级指针时,先访问一级指针,再访问一级指针指向的数据。
    & 是取地址操作符,用于获取变量的地址。当你对一个变量应用 & 时,编译器会根据变量的类型生成指针,并保存该指针的类型信息。

    编译器如何在内存中区分不同级别指针?
    一级指针(例如 int *ptr)会在内存中存储一个指向 int 类型数据的地址。
    二级指针(例如 int **pptr)会在内存中存储一个指向一级指针的地址,访问时会先取出一级指针,再使用一级指针去访问实际数据。

    cs 复制代码
    int x = 5;
    int *p1 = &x;   // p1 是一级指针,存储 x 的地址
    int **p2 = &p1;  // p2 是二级指针,存储 p1 的地址
    /*内存表示
    x = 5
    p1 -> 地址1    (指向 x)
    p2 -> 地址2    (指向 p1)
    */

    当我们解引用 p2 时,首先得到 p1 的值(即 &x),然后通过 p1 访问 x

  • 再次解读一个非常好的例子:

    cs 复制代码
    #include <stdio.h>
    
    int main(){
        int int_var = 16909060; // 0x01020304 in memory
        //16909060 对应的十六进制是 0x01020304。在小端模式内存中,int_var 会以4个字节存储这个值(0x04 0x03 0x02 0x01,从低地址到高地址)
        char *char_ptr = (char *)&int_var;
    
        printf("char_ptr[0]: %d\n", char_ptr[0]); // 输出最低字节
        printf("char_ptr[1]: %d\n", char_ptr[1]); // 输出次低字节
        /*
        上述的char_ptr[0]其实就是是指针数组的索引操作,实际上是访问char_ptr指向数组的第二个元素
        而char_ptr指向int_var的指针,而int_var被强制转化为char *型指针(偏移量为1)
        即int_var在内存中其实相当于四个元素的数组,第一个元素为0x04,第二个为0x03...
        char_ptr[0]也相当于对char_ptr的解引用:*char_ptr,故char_ptr[1]等价于*(char_ptr+1)
        */
        return 0;
    }

    根据以上分析可以推断出输出结果为:

    cs 复制代码
    char_ptr[0]: 4
    char_ptr[1]: 3

    指针词库

    现在我打算向你提供一个词库,用于读写指针。当你遇到复杂的指针语句时,试着参考它并且逐字拆分语句(或者不要使用这个语句,因为有可能并不好):

    type *ptr

    type类型的指针,名为ptr

    *ptr

    ptr所指向位置的值。

    *(ptr + i)

    ptr所指向位置加上i)的值。

    注:以字节为单位的话,应该是ptr所指向的位置再加上sizeof(type) * i

    &thing

    thing的地址。

    type *ptr = &thing

    名为ptrtype类型的指针,值设置为thing的地址。

    ptr++

    自增ptr指向的位置。

  • 使用访问指针的方式重写所有使用数组的地方。

    将原来使用 ages[i] 的地方,可以通过 *(ages + i) 来访问。

  • 使用访问数组的方式重写所有使用指针的地方。

    将原来使用 *(names + i) 的地方,可以通过 names[i] 来访问。

  • 使用指针来处理命令行参数,就像处理names那样。

    printf("Argument %d: %s\n", i, *(argv + i));

  • 在程序末尾添加一个for循环,打印出这些指针所指向的地址。你需要在printf中使用%p

    cs 复制代码
    // 打印指针所指向的地址
    void print_addresses(int *ages, char **names, int count)
    {
        int i = 0;
        while (i < count) {
            printf("Address of names[%d]: %p, Address of ages[%d]: %p\n", i, &names[i], i, &ages[i]);
            i++;
        }
    }
  • 对于每一种打印数组的方法,使用函数来重写程序。试着向函数传递指针来处理数据。记住你可以声明接受指针的函数,但是可以像数组那样用它。

    cs 复制代码
    #include <stdio.h>
    
    // 函数声明:打印年龄和名字
    void print_using_pointers(int *ages, char **names, int count);
    void print_using_arrays(int *ages, char **names, int count);
    void print_using_pointers_from_argv(char **argv, int argc);
    void print_addresses(int *ages, char **names, int count);
    
    int main(int argc, char *argv[])
    {
        // 创建数组
        int ages[] = {23, 43, 12, 89, 2};
        char *names[] = {"Alan", "Frank", "Mary", "John", "Lisa"};
    
        // 获取数组的元素个数
        int count = sizeof(ages) / sizeof(int);
    
        // 使用指针的方式打印
        print_using_pointers(ages, names, count);
    
        printf("---\n");
    
        // 使用数组的方式打印
        print_using_arrays(ages, names, count);
    
        printf("---\n");
    
        // 使用指针处理命令行参数
        print_using_pointers_from_argv(argv, argc);
    
        printf("---\n");
    
        // 打印指针所指向的地址
        print_addresses(ages, names, count);
    
        return 0;
    }
    
    // 使用指针方式打印
    void print_using_pointers(int *ages, char **names, int count)
    {
        int i = 0;
        while (i < count) {
            printf("%s is %d years old.\n", *(names + i), *(ages + i));
            i++;
        }
    }
    
    // 使用数组方式打印
    void print_using_arrays(int *ages, char **names, int count)
    {
        int i = 0;
        while (i < count) {
            printf("%s has %d years alive.\n", names[i], ages[i]);
            i++;
        }
    }
    
    // 使用指针处理命令行参数
    void print_using_pointers_from_argv(char **argv, int argc)
    {
        int i = 0;
        while (i < argc) {
            printf("Argument %d: %s\n", i, *(argv + i));
            i++;
        }
    }
    
    // 打印指针所指向的地址
    void print_addresses(int *ages, char **names, int count)
    {
        int i = 0;
        while (i < count) {
            printf("Address of names[%d]: %p, Address of ages[%d]: %p\n", i, (void*)&names[i], i, (void*)&ages[i]);
            i++;
        }
    }
  • for循环改为while循环,并且观察对于每种指针用法哪种循环更方便。

相关推荐
就叫飞六吧19 分钟前
Electron和C/C++开发桌面应用对比
c语言·开发语言·c++·visual studio
7yewh1 小时前
LeetCode 力扣 热题 100道(二十)三数之和(C++)
c语言·数据结构·c++·算法·leetcode
a0023450014 小时前
冒泡排序与快速排序以及用快速排序的思想改进我们的冒泡排序
c语言
Jambo!5 小时前
Visual studio中C/C++连接mysql
c语言·c++·mysql·visual studio
旷野..5 小时前
谁说C比C++快?
java·c语言·c++
~狂想家~6 小时前
使用C语言库函数格式化输入时格式类型与数据类型不匹配导致程序异常
c语言·开发语言
a0023450017 小时前
指针的深入讲解
c语言
学习前端的小z7 小时前
【C++ 】for 循环系统深入解析与实现法比较
c
爱吃西瓜的小菜鸡7 小时前
【C语言】贪心吃糖
c语言·开发语言·学习·算法