说明:
这是个人对该在Linux平台上的C语言学习网站笨办法学C上的每一个练习章节附加题的解析和回答
ex9:
- 将一些字符赋给
numbers
的元素,之后用printf
一次打印一个字符,你会得到什么编译器警告?
没有任何警告,列入给numbers的元素赋值字符'A'其实是赋给int类型变量字符'A'的ASCII码值65。
- 对
names
执行上述的相反操作,把names
当成int
数组,并一次打印一个int
,Valgrind
会提示什么?
只要赋值在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_int变量重新打印出Zed:name as integer: 6579546 name as hex: 0x64655a
这里涉及到一点知识点:数据的低位字节存于低地址,高位字节存于高地址。对于给定的字符数组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 *pointer
和 char *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 memorychar *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 元素复制到 statesstates[i] = argv[i]; // 将指针直接赋值i++; }
- 研究你是否真正复制了这些字符串。答案可能会让你感到意外和困惑。
在上述代码中,states[i] = argv[i];
只是复制了指针,而不是字符串本身。
这意味着states
和argv
的某些元素会指向相同的内存地址。
要真正复制字符串,需要分配新内存并拷贝内容:#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 argumentsfor(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 lowercaseif(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; }