Linux上的C语言编程实践

说明:

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

ex9:

  • 将一些字符赋给numbers的元素,之后用printf一次打印一个字符,你会得到什么编译器警告?

    没有任何警告,列入给numbers的元素赋值字符'A'其实是赋给int类型变量字符'A'的ASCII码值65。
     
  • names执行上述的相反操作,把names当成int数组,并一次打印一个intValgrind会提示什么?

    只要赋值在char类型变量的范围之内也不会报错。
     
  • 有多少种其它的方式可以用来打印它?

    可以使用循环来实现逐个打印元素:
    for (int i = 0; i < 4; i++) {
        printf("%c ", name[i]);
    }
    printf("\n");
    
    可以直接以字符串类型来自动全部打印出来:
    printf("%s\n", name);
    
    可以使用指针遍历打印:
    char *ptr = name;
    for (int i = 0; i < 4; i++) {
        printf("%c ", *(ptr + i));
    }
    printf("\n");
    
    或者:
     
    char *p = name;
    while (*p!= '\0') {
        putchar(*p);
        p++;
    }
  • 如果一个字符数组占四个字节,一个整数也占4个字节,你可以像整数一样使用整个name吗?你如何用黑魔法实现它?
    可以使用memcpy函数来复制:
     
    char name[] = "Zed";
    int name_as_int = 0;
    
    memcpy(&name_as_int, name, sizeof(name));
    
    printf("name as integer: %d\n", name_as_int);
    结果为:
    name as integer: 6579546
    name as hex: 0x64655a
    可以用name_as_int变量重新打印出Zed:
     
    printf("name_as_int is %c%c%c",name_as_int&0xff,name_as_int>>8&0xff,name_as_int>>16&0xff);
           这里涉及到一点知识点:数据的低位字节存于低地址,高位字节存于高地址。对于给定的字符数组name[4] = {'Z', 'e', 'd', '\0'},'Z'(ASCII 码值为 90,十六进制 0x5A)、'e'(ASCII 码值为 101,十六进制 0x65)、'd'(ASCII 码值为 100,十六进制 0x64)、'\0'(十六进制 0x00)。
           而对于'\0'空字符来说(ASCII 码值为 0,十六进制 0x00),int变量将其存在最高位所以导致了其自然消失了
     
  • 拿出一张纸,将每个数组画成一排方框,之后在纸上画出代码中的操作,看看是否正确。

    跳过
     
  • name转换成another的形式,看看代码是否能正常工作。
     
    void *ptr = name;
    printf("name as pointer: %p\n", ptr);
    

    可以正常工作

 ex10:

先做一个对指针内容的总结回顾:

char *states[] = { "California", "Oregon", "Washington", "Texas" };
char states[4][20] = {
    "California", "Oregon",
    "Washington", "Texas"
};

        其中char *states[] 是一个指针数组,数组中的每个元素是一个指向 char 的指针(即 char*)。而states 是一个二维数组,每一行固定分配了 20 个字符的空间。区别是:指针数组每个元素是一个指针,指向字符串的首地址;不需要为每个字符串分配固定长度的内存;字符串常量存储在只读区域;更灵活,可以指向不同大小的字符串或动态分配内存。而二维数组每个元素是一个固定大小的字符数组;每行占用固定大小的内存即便字符串较短);所有字符串存储在一个连续的内存块中;内存分配固定,不能指向其它字符串。


        对于指针指向类型的不同有:在计算机中,int *pointerchar *pointer 本质上都是指针,它们的大小只取决于系统的架构

  • 在 32 位系统中,指针占用 4 字节
  • 在 64 位系统中,指针占用 8 字节

        因此,无论是 int *pointer 还是 char *pointer,它们的大小通常是一样的,均为系统指针大小。 

//指向的数据类型不同
int *pointer //表示一个指针,它指向一个 int 类型的变量。
char *pointer //表示一个指针,它指向一个 char 类型的变量。
int *pointer //:步长为 sizeof(int)(通常是 4 字节或 8 字节)。
char *pointer //:步长为 sizeof(char)(始终是 1 字节)。

(步长不同)示例代码:

#include <stdio.h>

int main(){
    int int_var = 42;
    char char_var = 'A';

    int *int_ptr = &int_var;
    char *char_ptr = &char_var;

    printf("int_ptr: %p, int_ptr + 1: %p\n", int_ptr, int_ptr + 1);
    printf("char_ptr: %p, char_ptr + 1: %p\n", char_ptr, char_ptr + 1);
    return 0;
}

/*输出:
int_ptr: 00000030ac7ffc1c, int_ptr + 1: 00000030ac7ffc20
char_ptr: 00000030ac7ffc1b, char_ptr + 1: 00000030ac7ffc1c
*/

(指针解引用不同)示例代码:

//*int_ptr 读取一个 int 类型的值,占用 sizeof(int) 字节。
//*char_ptr 读取一个 char 类型的值,占用 1 字节。

#include <stdio.h>

int main(){
    int int_var = 16909060; // 0x01020304 in memory
    char *char_ptr = (char *)&int_var;

    printf("char_ptr[0]: %d\n", char_ptr[0]); // 输出最低字节
    printf("char_ptr[1]: %d\n", char_ptr[1]); // 输出次低字节

    return 0;
}

/*输出结果
char_ptr[0]: 4
char_ptr[1]: 3
*/
  • 弄清楚在for循环的每一部分你都可以放置什么样的代码。

    for (initialization; condition; increment) 是标准格式,其中每一部分可以放置以下内容: Initialization:可以放置任何类型的变量初始化或赋值语句
    Condition:可以是任何返回布尔值的表达式
    Increment:可以放置任何更新语句
     
  • 查询如何使用','(逗号)字符来在for循环的每一部分中,';'(分号)之间分隔多条语句
    例如
     
    for (int i = 0, j = 10; i < 5; i++, j--) {
        printf("i: %d, j: %d\n", i, j);
    }
    
  • 查询NULL是什么东西,尝试将它用做states的一个元素,看看它会打印出什么。

    NULL 是一个宏,表示空指针(即值为 0 的指针),用来指示某个指针未指向任何有效地址。
    在数组中使用 NULL:如果 states 数组中包含 NULL,表示该元素不指向任何字符串。
    示例:
    char *states[] = {
        "California", "Oregon",
        NULL, "Texas"
    };
    
    for (int i = 0; i < 4; i++) {
        printf("state %d: %s\n", i, states[i]);
    }
    
    
    /*输出结果
    state 0: California
    state 1: Oregon
    state 2: (null)
    state 3: Texas
    */
  • 看看你是否能在打印之前将states的一个元素赋值给argv中的元素,再试试相反的操作。
    // 修改 states 的元素为 argv 的元素
    states[0] = argv[1]; // 如果有命令行参数,将第一个参数赋值给 states[0]
    
    // 打印修改后的 states
    for (int i = 0; i < 4; i++) {
        printf("state %d: %s\n", i, states[i]);
    }
    
    // 反向操作:修改 argv 的元素为 states 的元素
    argv[1] = states[2]; // 将 states[2] 的值赋给 argv[1]
    printf("arg 1 after modification: %s\n", argv[1]);
    

ex11:

  • 让这些循环倒序执行,通过使用i--argc开始递减直到0。你可能需要做一些算数操作让数组的下标正常工作。
     
    int i = argc - 1;
    while (i >= 0) {
        printf("arg %d: %s\n", i, argv[i]);
        i--;
    }
    
    i = num_states - 1;  // 初始化为最后一个索引
    while (i >= 0) {
        printf("state %d: %s\n", i, states[i]);
        i--;
    }
    
  • 使用while循环将argv中的值复制到states
     
    i = 0;
    while (i < argc && i < num_states) {  // 确保不超出 states 数组范围
        states[i] = argv[i];
        i++;
    }
    
  • 让这个复制循环不会执行失败,即使argv之中有很多元素也不会全部放进states
     
    char **states = malloc(argc * sizeof(char *));  // 动态分配数组大小
    if (!states) {
        perror("Failed to allocate memory for states");
        return 1;
    }
    
    i = 0;
    while (i < argc) {  // 将所有 argv 元素复制到 states
        states[i] = argv[i];  // 将指针直接赋值
        i++;
    }
    
  • 研究你是否真正复制了这些字符串。答案可能会让你感到意外和困惑。

    在上述代码中,states[i] = argv[i]; 只是复制了指针,而不是字符串本身。
    这意味着 statesargv 的某些元素会指向相同的内存地址。
    要真正复制字符串,需要分配新内存并拷贝内容:
    #include <string.h> 
    
    states[i] = malloc(strlen(argv[i]) + 1);  // 为每个字符串分配内存,+1为了分配空间存储字符串末尾的空字符 \0
    if (states[i]) {
        strcpy(states[i], argv[i]);  // 复制字符串内容
    }
    

e12: 

  • 我已经向你简短地介绍了&&,它执行“与”操作。上网搜索与之不同的“布尔运算符”。

    其他常见的布尔运算符: ||:逻辑或,若任意一个条件为真,结果为真。 !:逻辑非,将条件取反。 &:按位与。 |:按位或。 ^:按位异或。

     
  • 回到练习10和11,使用if语句使循环提前退出。你需要break语句来实现它,搜索它的有关资料。

    例如:
    for(i = 1; i < argc; i++) {
        if(strcmp(argv[i], "stop") == 0) {
            printf("Found 'stop', exiting loop.\n");
            break;
        }
        printf("Argument %d: %s\n", i, argv[i]);
    }
    
  • 第一个判断所输出的话真的正确吗?由于你的“第一个参数”不是用户输入的第一个参数,把它改正。
     if(argc == 1) {
         printf("You only have no argument. You suck.\n");
     } else if(argc > 1 && argc < 4) {
         printf("Here's your arguments:\n");
                                                             
         for(i = 1; i < argc; i++) {
     			if(strcmp(argv[i], "stop") == 0) {
     				printf("Found 'stop', exiting loop.\n");
     				break;
     		}
             printf("%s ", argv[i]);
         }
         printf("\n");
     } else {
         printf("You have too many arguments. You suck.\n");
     }
    

ex14:

  • 编写另一个程序,在字母上做算术运算将它们转换为小写,并且在switch中移除所有额外的大写字母。
     
    //添加
    letter = letter + 32;
  • 使用','(逗号)在for循环中初始化letter
     
     for(int i = 0, letter = argv[arg][i]; letter != '\0'; i++, letter = argv[arg][i])

     
  • 使用另一个for循环来让它处理你传入的所有命令行参数。
     
    //在原来的for循环之前在添加一个for循环
    for(int arg = 1; arg < argc; arg++) {
            printf("Processing argument %d: %s\n", arg, argv[arg]);
    
            //.....
    }
  • 将这个switch语句转为if语句,你更喜欢哪个呢?

    if-else if的可读性更强,我会更喜欢使用if-else if
     
  • 在“Y”的例子中,我在if代码块外面写了个break。这样会产生什么效果?如果把它移进if代码块,会发生什么?自己试着解答它,并证明你是正确的。

    写在 if 外: 无论 if 条件是否满足,break 都直接跳出 switch 或 for,可能导致意外行为。 写在 if 内: 更精准,只有在条件满足时退出特定块。这里的 break 控制 Y 的特殊处理逻辑。

    修改后完整代码为:
    #include <stdio.h>
    
    int main(int argc, char *argv[]) {
        if(argc < 2) {
            printf("ERROR: You need at least one argument.\n");
            return 1;
        }
    
        // Process all arguments
        for(int arg = 1; arg < argc; arg++) {
            printf("Processing argument %d: %s\n", arg, argv[arg]);
    
            for(int i = 0, letter = argv[arg][i]; letter != '\0'; i++, letter = argv[arg][i]) {
                // Convert to lowercase
    						if(letter>=65&&letter<=90){
    							letter = letter+32;
    						}
                if(letter == 'a') {
                    printf("%d: 'A'\n", i);
                } else if(letter == 'e') {
                    printf("%d: 'E'\n", i);
                } else if(letter == 'i') {
                    printf("%d: 'I'\n", i);
                } else if(letter == 'o') {
                    printf("%d: 'O'\n", i);
                } else if(letter == 'u') {
                    printf("%d: 'U'\n", i);
                } else if(letter == 'y' && i > 2) {
                    printf("%d: 'Y'\n", i);
                    // Moving 'break' inside the condition limits its impact to this case only.
                    break;
                } else {
                    printf("%d: %c is not a vowel\n", i, letter);
                }
            }
        }
    
        return 0;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值