C语言指针与数组深入剖析及优化示例 指针解读 数组与指针的关系
说明:
这是个人对该在Linux平台上的C语言学习网站笨办法学C上的每一个练习章节附加题的解析和回答
ex14:
- 重新编写这些函数,使它们的数量减少。比如,你真的需要
can_print_it
吗?
if(isalpha(ch) || isblank(ch)) { printf("'%c' == %d ", ch, ch); }
- 使用
strlen
函数,让print_arguments
知道每个字符串参数都有多长,之后将长度传入print_letters
。然后重写print_letters
,让它只处理固定的长度,不按照'\0'
终止符。你需要#include <string.h>
来实现它。#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
来查询isalpha
和isblank
的信息。使用其它相似的函数来只打印出数字或者其它字符。
其他类似函数:
isdigit: 检查字符是否为数字(0-9)。
ispunct: 检查字符是否为标点符号。
isxdigit: 检查字符是否为十六进制数字。
isalnum: 检查字符是否为字母或数字。
iscntrl: 检查字符是否为控制字符。#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编程中已经过时,但仍然可以在一些老旧的代码中看到。它的特点是函数参数的类型声明放在函数体之前,单独列出,而不是在函数头部直接定义参数及其类型。int sum(a, b) int a; // 参数类型声明 int b; // 参数类型声明 { return a + b; }
ex15:
- 一些指针的疑问总结:
指针的类型和不同级数的异同点?
所有的指针(无论几级指针都是一个存储指针地址的变量,都是只有操作系统的位数来决定的,要么是64位要么是32位),而指针的类型(例如:int * ,char *)决定了指针每次读取数据的内存地址偏移量
例子:#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)会在内存中存储一个指向一级指针的地址,访问时会先取出一级指针,再使用一级指针去访问实际数据。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
。 -
再次解读一个非常好的例子:
#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; }
根据以上分析可以推断出输出结果为:
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
名为
ptr
,type
类型的指针,值设置为thing
的地址。ptr++
自增
ptr
指向的位置。
- 使用访问指针的方式重写所有使用数组的地方。
将原来使用ages[i]
的地方,可以通过*(ages + i)
来访问。
- 使用访问数组的方式重写所有使用指针的地方。
将原来使用*(names + i)
的地方,可以通过names[i]
来访问。
- 使用指针来处理命令行参数,就像处理
names
那样。
printf("Argument %d: %s\n", i, *(argv + i));
- 在程序末尾添加一个
for
循环,打印出这些指针所指向的地址。你需要在printf
中使用%p
。
// 打印指针所指向的地址 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++; } }
- 对于每一种打印数组的方法,使用函数来重写程序。试着向函数传递指针来处理数据。记住你可以声明接受指针的函数,但是可以像数组那样用它。
#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
循环,并且观察对于每种指针用法哪种循环更方便。