C Primer Plus学习笔记【9-10章节】
9.1 复习函数
函数(function)是完成特定任务的独立程序代码单元,函数具备两个功能,执行某些动作,返回一个值供程序是使用。
9.11创建并使用简单函数
#include <stdio.h> #define NAME "GICATHINK, INC." #define ADDRESS "101 Megabuck Plaza" #define PLACE "Megapolis, CA 94904" #define WIDTH 40 void statbar(void); /* 函数原型 */ int main(void) { statbar(); printf("%s\n", NAME); printf("%s\n", ADDRESS); printf("%s\n", PLACE); statbar(); return 0; } void statbar(void) /* 定义函数 */ { int count; for (count = 1; count <= WIDTH; count++) { putchar('*'); } putchar('\n'); }
输出
****************************************
GICATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
Program ended with exit code: 0
9.1.2分析程序
略
9.1.3函数参数
使用带有参数的函数
#include <stdio.h> #include <string.h> /* 为strlen()提供原型 */ #define NAME "GICATHINK, INC." #define ADDRESS "101 Megabuck Plaza" #define PLACE "Megapolis, CA 94904" #define WIDTH 40 #define SPACE ' ' void show_n_char(char ch, int num); int main(void){ int spaces; show_n_char('*', WIDTH); putchar('\n'); show_n_char(SPACE, 12); printf("%s\n", NAME); spaces = (WIDTH - strlen(ADDRESS)) / 2; /* 计算需要跳过的空格 */ show_n_char(SPACE, spaces); printf("%s\n", ADDRESS); show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2); printf("%s\n", PLACE); show_n_char('*', WIDTH); putchar('\n'); return 0; } void show_n_char(char ch, int num){ int count; for (count = 1; count <= num; count++) { putchar(ch); } }
输出
****************************************
GICATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
9.1.4 定义带形式参数的函数
略
9.1.5 声明带形式参数函数的原型
void show_n_char(char, int);
可以在里面不写变量名,在原型中使用变量名并没有实际创建变量。
9.1.6 调用带实际参数的函数
略
9.1.7 黑盒视角
函数的形参,内部定义变量都属于局部变量,在主函数中定义相同变量,不会影响局部变量,局部变量也不会影响主函数的变量。
9.1.8 使用return从函数中返回值
示例代码
#include <stdio.h> int imin(int, int); int main(void) { int evil1, evil2; printf("Enter a pair of integers (q to quit):\n"); while (scanf("%d %d", &evil1, &evil2) == 2) { printf("The lesser of %d and %d is %d.\n", evil1,evil2, imin(evil1, evil2)); printf("Enter a pair of integers (q to quit):\n"); } printf("Bye.\n"); return 0; } int imin(int n, int m){ int min; if (n < m) { min = n; } else min = m; return min; }
输出
Enter a pair of integers (q to quit):
50 100
The lesser of 50 and 100 is 50.
Enter a pair of integers (q to quit):
20 10
The lesser of 20 and 10 is 10.
Enter a pair of integers (q to quit):
qBye.
改进用条件表达式就只用一条语句
#include <stdio.h> int imin(int, int); int main(void) { int evil1, evil2; printf("Enter a pair of integers (q to quit):\n"); while (scanf("%d %d", &evil1, &evil2) == 2) { printf("The lesser of %d and %d is %d.\n", evil1,evil2, imin(evil1, evil2)); printf("Enter a pair of integers (q to quit):\n"); } printf("Bye.\n"); return 0; } int imin(int n, int m){ return (n < m) ? n : m; }
还可以改进用两个return,代码略
return可以后面不加东西,会终止函数。
9.1.9 函数类型
要正确的使用函数,程序在第1次使用函数之前就必须知道函数的类型。方法之一是,把完整的函数定义放在第1次调用函数的前面。第二种就是提前声明函数,把函数信息告知编译器。
9.2 ANSI C函数原型
在ANSI C标准之前,申明函数只需要声明函数的类型,不用声明烦人喝参数。
比如 int imin();
9.2.1问题所在
错误示例代码
#include <stdio.h> int imax(); int main(void) { printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3)); printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3.0 , 5.0)); return 0; } int imax(n, m) int n, m; { return (n >m ? n : m); }
错误输出
The maxinum of 3 and 5 is 3.
The maxinum of 3 and 5 is 0.
Program ended with exit code: 0
这个是因为主函数把它的参数存储在被称为栈(stack)的临时存储区,被调函数从栈中读取这些参数。对于该例子,这两个过程并未相互协调。主调函数根据函数调用中的实际参数决定传递的类型,而被调函数根据它的形参读取值。这就出现问题了,int是32为的,double是128位的,float作为参数传参时会升级为double
9.2.2ANSI的解决方案。
就是在函数原型(function prototype)来声明函数的返回类型,参数的数量和每个参数的类型。
#include <stdio.h> int imax(int, int); int main(void) { printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3, 5)); printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3.0 , 5.0)); return 0; } int imax(n, m) int n, m; { return (n >m ? n : m); }
这样在函数在调用的时候,会把实际参数的类型转换承形式参数的类型。
9.2.3 无参数和未指定参数
用void print_name(void);
后面将介绍通过*接受不定长参数,如printf,scanf
9.2.4 函数原型的有点
函数原型好处很多,要用起来,如果为较小的函数,可以写在被调用之前,此时函数的定义也相当于函数原型
int imax(int a, int b) { return a > b ? a: b}
int main(void)
{
...
z = imax......
return 0;
}
9.3 递归
本人对递归看到就头痛,递归有时候更简洁,但效率没有循环高。
9.3.1演示递归
#include <stdio.h> void up_and_down(int); int main(void) { up_and_down(1); return 0; } void up_and_down(int n) { printf("Level %d: n location %p\n",n , &n); /* %p格式化输出地址,&n取对象地址 */ if (n < 4) { up_and_down(n + 1); } printf("LEVEL %d: n location %p\n",n , &n); /* %p格式化输出地址,&n取对象地址 */ }
输出
Level 1: n location 0x7ffeefbff46c
Level 2: n location 0x7ffeefbff44c
Level 3: n location 0x7ffeefbff42c
Level 4: n location 0x7ffeefbff40c
LEVEL 4: n location 0x7ffeefbff40c
LEVEL 3: n location 0x7ffeefbff42c
LEVEL 2: n location 0x7ffeefbff44c
LEVEL 1: n location 0x7ffeefbff46c
Program ended with exit code: 0
书中介绍,略
9.3.2 递归的基本原理
略
9.3.3 尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在return语句之前。这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的尾巴。尾递归是最简单的递归形式,因为它相当于循环。
示例代码,求一个数的阶乘(factorial)
#include <stdio.h> long fact(int); long rfact(int); int main(void){ int num; printf("This program calculates factorials.\n"); printf("Enter a value in the range 0-12 (q to quit):\n"); while (scanf("%d", &num) == 1) { if (num < 0) { printf("No negative numbers, please.\n"); } else if (num > 12) printf("Keep input under 13.\n"); else { printf("loop: %d factorial = %ld\n", num, fact(num)); printf("recursion: %d factorial = %ld\n", num, rfact(num)); } } return 0; } long fact(int n) { long ans; for (ans = 1; n >1; n--) { ans *=n; } return ans; } long rfact(int n) { long ans; if (n > 0) { ans = n * rfact(n -1); } else ans = 1; return ans; }
输出
This program calculates factorials.
Enter a value in the range 0-12 (q to quit):
10
loop: 10 factorial = 3628800
recursion: 10 factorial = 3628800
7
loop: 7 factorial = 5040
recursion: 7 factorial = 5040
q
Program ended with exit code: 0
一般使用循环比较好。首先,每次递归都会创建一组变量,所有递归使用内存更多,而且每次递归调用都会把创建的一组新变量放在栈中。递归调用的数量受限与内存空间。其次每次函数调用要花费一定的时间,所以递归的执行速度比较满。
9.3.4 递归和倒序计算。
本节书中用了10进制转换成2进制,用了除2取余的方式取二进制。示例代码
#include <stdio.h> void to_binary(unsigned long); int main(void) { unsigned long number; printf("Enter an integer (q to quit): \n"); while (scanf("%lu", &number) == 1) { printf("Binary equivalent: "); to_binary(number); putchar('\n'); printf("Enter an integer (q to quit):\n"); } printf("Done.\n"); return 0; } void to_binary(unsigned long n) { int r; r = n % 2; // 取余数,最后一个数字,先进后出 if (n >= 2) { to_binary(n / 2); } putchar(r == 0 ? '0' : '1'); return; }
上面写了一个注释,很好的使用了递归的特性,先进后出的逻辑。
9.3.5 递归的优缺点
书中介绍了斐波那契数列,运用双递归的方式(double recursion),内存的占用将指数倍的增长,确实夸张。
递归的优点,确实为某些编程问题提供了最简单的解决方案。但缺点是一些递归算法会快速消耗计算机的内存资源。
9.4 编译多源代码文件的程序。
C语言蛮好,同一个文件下,不用倒包,最多用一个头文件就好了,里面可以定义一些常量,还有一些函数原型。
书中案例一个项目下面,用了两个C的文件,一个.h的头文件。
源代码
// // usehotel.c // sd // // Created by sidian on 2020/12/23. // Copyright © 2020 sidian. All rights reserved. // #include <stdio.h> #include "hotel.h" // 倒入自定义的头文件 int main(void) { int nights; double hotel_rate; int code; while ((code = menu()) != QUIT) { switch (code) { case 1: hotel_rate = HOTEL1; break; case 2: hotel_rate = HOTEL2; break; case 3: hotel_rate = HOTEL3; break; case 4: hotel_rate = HOTEL4; break; default: hotel_rate = .0; printf("Oop!\n"); break; } nights = getnights(); showprice(hotel_rate, nights); } printf("Thank you and goodbye.\n"); return 0; }
// // hotel.c // sd // // Created by sidian on 2020/12/23. // Copyright © 2020 sidian. All rights reserved. // // #include <stdio.h> #include "hotel.h" int menu(void) { int code, status; printf("\n%s%s\n", STARS, STARS); printf("Enter the number of the desired hotel:\n"); printf("1) Fairfield Arms 2) Hotel Olympic\n"); printf("3) Chertworthy Plaza 4) The Stockton\n"); printf("5) quit\n"); printf("%s%s\n", STARS, STARS); while ((status = scanf("%d", &code)) != 1 || (code < 1 || code >5)) { if (status != 1) { scanf("%*s"); // 处理非整数的输入,中间不能有空格 } printf("Enter an integer from 1 to 5, please.\n"); } return code; } int getnights(void) { int nights; printf("How mant nights are needed? "); while (scanf("%d", &nights) != 1) { scanf("%*s"); printf("Please enter an integer, such as 2.\n"); } return nights; } void showprice(double rate, int nights) { int n; double total = .0; double factor = 1.0; for (n = 1; n <= nights; n++, factor *= DISCOUNT) { total += rate * factor; } printf("The total cost will be $%0.2f\n", total); }
// // hotel.h // sd // // Created by sidian on 2020/12/23. // Copyright © 2020 sidian. All rights reserved. // #ifndef hotel_h #define hotel_h #endif /* hotel_h */ #define QUIT 5 #define HOTEL1 180.00 #define HOTEL2 225.00 #define HOTEL3 255.00 #define HOTEL4 355.00 #define DISCOUNT 0.95 #define STARS "****************" // 显式选择列表 原型函数 int menu(void); // 返回预订天数 int getnights(void); // 根据费率、入住天数计算费用,并显式结果 void showprice(double rate, int nights);
书中介绍了用scanf返回的值来判断是否接受到了正确的值,并且用scanf(%*s)来处理错误的输入。
我个人感觉不是非常好的方法,当输入sada 2的时候,会处理前面的英文,然后读取后面的2的输入,但错误提示还是会有,这样的用户体验应该不好。
我个人觉的用getchar来处理更加合适,当读取到'\n'停止
9.5 查找地址: &运算符
指针(pointer)是C语言最重要的(有时也是最复杂的)概念之一,用于存储变量的地址。
------------恢复内容开始------------
9.1 复习函数
函数(function)是完成特定任务的独立程序代码单元,函数具备两个功能,执行某些动作,返回一个值供程序是使用。
9.11创建并使用简单函数
#include <stdio.h> #define NAME "GICATHINK, INC." #define ADDRESS "101 Megabuck Plaza" #define PLACE "Megapolis, CA 94904" #define WIDTH 40 void statbar(void); /* 函数原型 */ int main(void) { statbar(); printf("%s\n", NAME); printf("%s\n", ADDRESS); printf("%s\n", PLACE); statbar(); return 0; } void statbar(void) /* 定义函数 */ { int count; for (count = 1; count <= WIDTH; count++) { putchar('*'); } putchar('\n'); }
输出
****************************************
GICATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
Program ended with exit code: 0
9.1.2分析程序
略
9.1.3函数参数
使用带有参数的函数
#include <stdio.h> #include <string.h> /* 为strlen()提供原型 */ #define NAME "GICATHINK, INC." #define ADDRESS "101 Megabuck Plaza" #define PLACE "Megapolis, CA 94904" #define WIDTH 40 #define SPACE ' ' void show_n_char(char ch, int num); int main(void){ int spaces; show_n_char('*', WIDTH); putchar('\n'); show_n_char(SPACE, 12); printf("%s\n", NAME); spaces = (WIDTH - strlen(ADDRESS)) / 2; /* 计算需要跳过的空格 */ show_n_char(SPACE, spaces); printf("%s\n", ADDRESS); show_n_char(SPACE, (WIDTH - strlen(PLACE)) / 2); printf("%s\n", PLACE); show_n_char('*', WIDTH); putchar('\n'); return 0; } void show_n_char(char ch, int num){ int count; for (count = 1; count <= num; count++) { putchar(ch); } }
输出
****************************************
GICATHINK, INC.
101 Megabuck Plaza
Megapolis, CA 94904
****************************************
9.1.4 定义带形式参数的函数
略
9.1.5 声明带形式参数函数的原型
void show_n_char(char, int);
可以在里面不写变量名,在原型中使用变量名并没有实际创建变量。
9.1.6 调用带实际参数的函数
略
9.1.7 黑盒视角
函数的形参,内部定义变量都属于局部变量,在主函数中定义相同变量,不会影响局部变量,局部变量也不会影响主函数的变量。
9.1.8 使用return从函数中返回值
示例代码
#include <stdio.h> int imin(int, int); int main(void) { int evil1, evil2; printf("Enter a pair of integers (q to quit):\n"); while (scanf("%d %d", &evil1, &evil2) == 2) { printf("The lesser of %d and %d is %d.\n", evil1,evil2, imin(evil1, evil2)); printf("Enter a pair of integers (q to quit):\n"); } printf("Bye.\n"); return 0; } int imin(int n, int m){ int min; if (n < m) { min = n; } else min = m; return min; }
输出
Enter a pair of integers (q to quit):
50 100
The lesser of 50 and 100 is 50.
Enter a pair of integers (q to quit):
20 10
The lesser of 20 and 10 is 10.
Enter a pair of integers (q to quit):
qBye.
改进用条件表达式就只用一条语句
#include <stdio.h> int imin(int, int); int main(void) { int evil1, evil2; printf("Enter a pair of integers (q to quit):\n"); while (scanf("%d %d", &evil1, &evil2) == 2) { printf("The lesser of %d and %d is %d.\n", evil1,evil2, imin(evil1, evil2)); printf("Enter a pair of integers (q to quit):\n"); } printf("Bye.\n"); return 0; } int imin(int n, int m){ return (n < m) ? n : m; }
还可以改进用两个return,代码略
return可以后面不加东西,会终止函数。
9.1.9 函数类型
要正确的使用函数,程序在第1次使用函数之前就必须知道函数的类型。方法之一是,把完整的函数定义放在第1次调用函数的前面。第二种就是提前声明函数,把函数信息告知编译器。
9.2 ANSI C函数原型
在ANSI C标准之前,申明函数只需要声明函数的类型,不用声明烦人喝参数。
比如 int imin();
9.2.1问题所在
错误示例代码
#include <stdio.h> int imax(); int main(void) { printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3)); printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3.0 , 5.0)); return 0; } int imax(n, m) int n, m; { return (n >m ? n : m); }
错误输出
The maxinum of 3 and 5 is 3.
The maxinum of 3 and 5 is 0.
Program ended with exit code: 0
这个是因为主函数把它的参数存储在被称为栈(stack)的临时存储区,被调函数从栈中读取这些参数。对于该例子,这两个过程并未相互协调。主调函数根据函数调用中的实际参数决定传递的类型,而被调函数根据它的形参读取值。这就出现问题了,int是32为的,double是128位的,float作为参数传参时会升级为double
9.2.2ANSI的解决方案。
就是在函数原型(function prototype)来声明函数的返回类型,参数的数量和每个参数的类型。
#include <stdio.h> int imax(int, int); int main(void) { printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3, 5)); printf("The maxinum of %d and %d is %d.\n", 3, 5, imax(3.0 , 5.0)); return 0; } int imax(n, m) int n, m; { return (n >m ? n : m); }
这样在函数在调用的时候,会把实际参数的类型转换承形式参数的类型。
9.2.3 无参数和未指定参数
用void print_name(void);
后面将介绍通过*接受不定长参数,如printf,scanf
9.2.4 函数原型的有点
函数原型好处很多,要用起来,如果为较小的函数,可以写在被调用之前,此时函数的定义也相当于函数原型
int imax(int a, int b) { return a > b ? a: b}
int main(void)
{
...
z = imax......
return 0;
}
9.3 递归
本人对递归看到就头痛,递归有时候更简洁,但效率没有循环高。
9.3.1演示递归
#include <stdio.h> void up_and_down(int); int main(void) { up_and_down(1); return 0; } void up_and_down(int n) { printf("Level %d: n location %p\n",n , &n); /* %p格式化输出地址,&n取对象地址 */ if (n < 4) { up_and_down(n + 1); } printf("LEVEL %d: n location %p\n",n , &n); /* %p格式化输出地址,&n取对象地址 */ }
输出
Level 1: n location 0x7ffeefbff46c
Level 2: n location 0x7ffeefbff44c
Level 3: n location 0x7ffeefbff42c
Level 4: n location 0x7ffeefbff40c
LEVEL 4: n location 0x7ffeefbff40c
LEVEL 3: n location 0x7ffeefbff42c
LEVEL 2: n location 0x7ffeefbff44c
LEVEL 1: n location 0x7ffeefbff46c
Program ended with exit code: 0
书中介绍,略
9.3.2 递归的基本原理
略
9.3.3 尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在return语句之前。这种形式的递归被称为尾递归(tail recursion),因为递归调用在函数的尾巴。尾递归是最简单的递归形式,因为它相当于循环。
示例代码,求一个数的阶乘(factorial)
#include <stdio.h> long fact(int); long rfact(int); int main(void){ int num; printf("This program calculates factorials.\n"); printf("Enter a value in the range 0-12 (q to quit):\n"); while (scanf("%d", &num) == 1) { if (num < 0) { printf("No negative numbers, please.\n"); } else if (num > 12) printf("Keep input under 13.\n"); else { printf("loop: %d factorial = %ld\n", num, fact(num)); printf("recursion: %d factorial = %ld\n", num, rfact(num)); } } return 0; } long fact(int n) { long ans; for (ans = 1; n >1; n--) { ans *=n; } return ans; } long rfact(int n) { long ans; if (n > 0) { ans = n * rfact(n -1); } else ans = 1; return ans; }
输出
This program calculates factorials.
Enter a value in the range 0-12 (q to quit):
10
loop: 10 factorial = 3628800
recursion: 10 factorial = 3628800
7
loop: 7 factorial = 5040
recursion: 7 factorial = 5040
q
Program ended with exit code: 0
一般使用循环比较好。首先,每次递归都会创建一组变量,所有递归使用内存更多,而且每次递归调用都会把创建的一组新变量放在栈中。递归调用的数量受限与内存空间。其次每次函数调用要花费一定的时间,所以递归的执行速度比较满。
9.3.4 递归和倒序计算。
本节书中用了10进制转换成2进制,用了除2取余的方式取二进制。示例代码
#include <stdio.h> void to_binary(unsigned long); int main(void) { unsigned long number; printf("Enter an integer (q to quit): \n"); while (scanf("%lu", &number) == 1) { printf("Binary equivalent: "); to_binary(number); putchar('\n'); printf("Enter an integer (q to quit):\n"); } printf("Done.\n"); return 0; } void to_binary(unsigned long n) { int r; r = n % 2; // 取余数,最后一个数字,先进后出 if (n >= 2) { to_binary(n / 2); } putchar(r == 0 ? '0' : '1'); return; }
上面写了一个注释,很好的使用了递归的特性,先进后出的逻辑。
9.3.5 递归的优缺点
书中介绍了斐波那契数列,运用双递归的方式(double recursion),内存的占用将指数倍的增长,确实夸张。
递归的优点,确实为某些编程问题提供了最简单的解决方案。但缺点是一些递归算法会快速消耗计算机的内存资源。
9.4 编译多源代码文件的程序。
C语言蛮好,同一个文件下,不用倒包,最多用一个头文件就好了,里面可以定义一些常量,还有一些函数原型。
书中案例一个项目下面,用了两个C的文件,一个.h的头文件。
源代码
// // usehotel.c // sd // // Created by sidian on 2020/12/23. // Copyright © 2020 sidian. All rights reserved. // #include <stdio.h> #include "hotel.h" // 倒入自定义的头文件 int main(void) { int nights; double hotel_rate; int code; while ((code = menu()) != QUIT) { switch (code) { case 1: hotel_rate = HOTEL1; break; case 2: hotel_rate = HOTEL2; break; case 3: hotel_rate = HOTEL3; break; case 4: hotel_rate = HOTEL4; break; default: hotel_rate = .0; printf("Oop!\n"); break; } nights = getnights(); showprice(hotel_rate, nights); } printf("Thank you and goodbye.\n"); return 0; }
// // hotel.c // sd // // Created by sidian on 2020/12/23. // Copyright © 2020 sidian. All rights reserved. // // #include <stdio.h> #include "hotel.h" int menu(void) { int code, status; printf("\n%s%s\n", STARS, STARS); printf("Enter the number of the desired hotel:\n"); printf("1) Fairfield Arms 2) Hotel Olympic\n"); printf("3) Chertworthy Plaza 4) The Stockton\n"); printf("5) quit\n"); printf("%s%s\n", STARS, STARS); while ((status = scanf("%d", &code)) != 1 || (code < 1 || code >5)) { if (status != 1) { scanf("%*s"); // 处理非整数的输入,中间不能有空格 } printf("Enter an integer from 1 to 5, please.\n"); } return code; } int getnights(void) { int nights; printf("How mant nights are needed? "); while (scanf("%d", &nights) != 1) { scanf("%*s"); printf("Please enter an integer, such as 2.\n"); } return nights; } void showprice(double rate, int nights) { int n; double total = .0; double factor = 1.0; for (n = 1; n <= nights; n++, factor *= DISCOUNT) { total += rate * factor; } printf("The total cost will be $%0.2f\n", total); }
// // hotel.h // sd // // Created by sidian on 2020/12/23. // Copyright © 2020 sidian. All rights reserved. // #ifndef hotel_h #define hotel_h #endif /* hotel_h */ #define QUIT 5 #define HOTEL1 180.00 #define HOTEL2 225.00 #define HOTEL3 255.00 #define HOTEL4 355.00 #define DISCOUNT 0.95 #define STARS "****************" // 显式选择列表 原型函数 int menu(void); // 返回预订天数 int getnights(void); // 根据费率、入住天数计算费用,并显式结果 void showprice(double rate, int nights);
书中介绍了用scanf返回的值来判断是否接受到了正确的值,并且用scanf(%*s)来处理错误的输入。
我个人感觉不是非常好的方法,当输入sada 2的时候,会处理前面的英文,然后读取后面的2的输入,但错误提示还是会有,这样的用户体验应该不好。
我个人觉的用getchar来处理更加合适,当读取到'\n'停止
9.5 查找地址: &运算符[终于到指针了,小激动]
指针(pointer)是C语言最重要的(有时也是最复杂的)概念之一,用于存储变量的地址。
同&符号也就是&variable是变量的地址
示例代码
#include <stdio.h> void mikado(int); int main(void) { int pooh = 2, bah = 5; printf("In main(), pooh = %d and &pooh = %p\n", pooh, &pooh); printf("In main(), bah = %d and &pooh = %p\n", bah, &bah); mikado(pooh); return 0; } void mikado(int bah){ int pooh = 10; printf("In mikado(), pooh = %d and &pooh = %p\n", pooh, &pooh); printf("In mikado(), bah = %d and &pooh = %p\n", bah, &bah); }
输出
In main(), pooh = 2 and &pooh = 0x7ffeefbff488
In main(), bah = 5 and &pooh = 0x7ffeefbff484
In mikado(), pooh = 10 and &pooh = 0x7ffeefbff458
In mikado(), bah = 2 and &pooh = 0x7ffeefbff45c
C语言每个函数都有自己的变量,这个跟Python不一样,Python属于引用传参。
9.6 更改主调函数中的变量
示例代码
略
9.7 指针介绍
指针(poiner)是一个值为内存地址的变量(或数据对象)
9.7.1 间接运算符: *
&变量名是变量名的地址
*地址给出了在指针指向地址上的值。
9.7.2 声明指针
通过 int * p 或者 char * c等方式声明指针。
*与指针名之间的空格可有可无。通常,程序员在声明时使用空格,在解引用变量时省略空格。指针时一个新类型,所以格式化输出需要用%p来转换
9.7.3 使用指针在函数间通信
通过指针,调用函数修改外部的值,通过传入地址,然后*地址的方式修改外部函数的值。
示例代码
#include <stdio.h> void interchange(int *, int *); int main(void) { int x= 5, y = 10; printf("Originally x = %d and y = %d.\n", x ,y); interchange(&x, &y); // 传入变量地址 printf("Now x = %d and y = %d.\n", x ,y); return 0; } void interchange(int * u, int * v) { int temp; temp = *u; // 这里的*u就时main函数的x的值 *u = *v; *v = temp; }
输出
Originally x = 5 and y = 10.
Now x = 10 and y = 5.
简而言之,普通变量把值作为基数量,把地址作为通过&运算符获得的派生量,而指针变量把地址作为基数量,把值作为通过*运算符获得的派生量。
9.8----9.9
略
9.10 复习题
第一题
抄答案: 形式参数时定义在被调用函数中的变量。实际参数是出现在函数调用中的值,该值被赋给形式参数。
可以把实际参数理解为在函数调用时初始化形式参数的值。
第二题
a. void dount(int n)
b. int gear(int n, int m)
c.int guess(void)
d.void stuff_it(double d, double * pd)
第三题
a. char n_to_char(int n)
b. int digit(double n, int m)
c. double * which(double * n, double * m)
d.int random(void)
第四题
int add(int u, int v) { return u + v; }
第五题
double add(double u, double v) { return u + v; }
第六题
void alter(int * u, int * v) { int m1, m2; m1 = *u; m2 = *v; *u = m1 + m2; *v = m1 - m2; }
答案的写法:
void alter(int * u, int * v) { *u = *u + *v; *v = *u - 2 * *p; }
答案就是坏啊,都不用中间变量。
第七题
不正确
第八题
int max(int a, int b, int c) { int max_num; max_num = a > b ? a : b; max_num = max_num > c ? max_num : c; return max_num; }
第九题
#include <stdio.h> void display(void); int choice_range(int, int); int main(void) { int in_num; while (1) { display(); in_num = choice_range(1, 4); switch (in_num) { case 1: printf("copy files \n"); break; case 2: printf("move files \n"); break; case 3: printf("remove files \n"); break; case 4: printf("Bye.\n"); return 0; default: break; } } return 0; } void display(void){ printf("Please choose one of the following:\n"); printf("1) copy files 2) move files\n"); printf("3) remove files 4) quit\n"); printf("Enter the number or your choice:"); } int choice_range(int min, int max){ int in_num; while (scanf("%d", &in_num) == 1) { if (in_num > max || in_num < min) { display(); } else return in_num; } return 4; }
输出
Please choose one of the following:
1) copy files 2) move files
3) remove files 4) quit
Enter the number or your choice:12
Please choose one of the following:
1) copy files 2) move files
3) remove files 4) quit
Enter the number or your choice:3
remove files
Please choose one of the following:
1) copy files 2) move files
3) remove files 4) quit
Enter the number or your choice:2
move files
Please choose one of the following:
1) copy files 2) move files
3) remove files 4) quit
Enter the number or your choice:q
Bye.
Program ended with exit code: 0
9.11 编程练习
第一题
#include <stdio.h> double min(double ,double); int main(void) { double x =5.5, y =6; double res; res = min(x, y); printf("res = %f.\n", res); return 0; } double min(double x, double y) { return x > y ? y : x; }
第二题
#include <stdio.h> void print_ok(char, int, int); int main(void) { int x =3, y =6; char ch = '$'; print_ok(ch, x, y); return 0; } void print_ok(char ch, int x, int y) { int row, column; for (row = 1; row <= x; row++) { for (column =1; column <= y; column++) { putchar(ch); } putchar('\n'); } }
第三题
#include <stdio.h> void print_ok(char, int, int); int main(void) { int x =3, y =6; char ch = '$'; print_ok(ch, x, y); return 0; } void print_ok(char ch, int y, int x) { int row, column; for (row = 1; row <= x; row++) { for (column =1; column <= y; column++) { putchar(ch); } putchar('\n'); } }
只不过在第二题的基础上,行与列的顺序换了以下而已。
第四题
#include <stdio.h> double aver_func(double, double); int main(void) { double res; res = aver_func(3, 2); printf("res is : %.2f.\n", res); return 0; } double aver_func(double num1, double num2) { double res; res = 1 / ((1/num1 + 1/num2) / 2); return res; }
输出
res is : 2.40.
第五题
#include <stdio.h> void larger(double *, double *); int main() { double x = 1.98, y = -1.9; larger(&x, &y); printf("x is %f, y is %f.\n", x ,y); return 0; } void larger(double * x, double * y) { double temp; temp = *x > *y ? *x : *y; *x = temp; *y = temp; }
输出
x is 1.980000, y is 1.980000.
第六题
#include <stdio.h> void myfunc(double *, double *, double *); int main(void) { double n1 = 9.1, n2 = 3.9, n3 = 2.5; myfunc(&n1, &n2, &n3); printf("n1 is %f, n2 is %f, n3 is %f.\n", n1, n2, n3); return 0; } void myfunc(double * n1, double * n2, double * n3) { double min, middle, max; if (*n1 > *n2) { if (*n3 > *n1) { max = *n3; middle = *n1; min = *n2; } else { max = *n1; if (*n2 > *n3) { middle = *n2; min = *n3; } else{ middle = *n3; min = *n2; } } } else{ if (*n3 < *n1) { max = *n2; middle = *n1; min = *n3; } else { min = *n1; if (*n2 > *n3) { max = *n2; middle = *n3; } else{ max = *n3; middle = *n2; } } } *n1 = min; *n2 = middle; *n3 = max; }
输出
n1 is 2.500000, n2 is 3.900000, n3 is 9.100000.
第七题
#include <stdio.h> #include <ctype.h> int char_index(char); int main(void) { char ch; int ch_index; printf("Content is :\n"); while ((ch = getchar()) != EOF) { ch_index = char_index(ch); printf("%c => %d\t", ch, ch_index); } } int char_index(char ch) { char first_ch = 'a'; int re_num; if (isalpha(ch)) { re_num = tolower(ch) - first_ch + 1; } else re_num = -1; return re_num; }
第八题
#include <stdio.h> double power(double, int); int main(void) { double x, xpow; int exp; printf("Enter a number and the positive integer power"); printf(" to which\nthe number will be raised. Enter q"); printf(" to quit.\n"); while (scanf("%lf%d", &x, &exp) == 2) { xpow = power(x, exp); printf("%.3g to the power %d is %.5g\n", x, exp, xpow); printf("Enter next pair of numbers or q to quit.\n"); } printf("Hope you enjoyed this power trip -- bye!\n"); return 0; } double power(double n, int p) { double pow = 1; int i; if (n == 0 && p != 0) { pow = 0; } if (p > 0) { for (i = 1; i <= p; i++) { pow *= n; } } else if (p < 0){ p = -p; for (i = 1; i <= p; i++) { pow *= n; } pow = 1 / pow; } else{ if (n == 0) { printf("0的0次方未定义,默认返回为1"); } pow = 1; } return pow; }
第9题
#include <stdio.h> double power(double, int); int main(void) { double x, xpow; int exp; printf("Enter a number and the positive integer power"); printf(" to which\nthe number will be raised. Enter q"); printf(" to quit.\n"); while (scanf("%lf%d", &x, &exp) == 2) { xpow = power(x, exp); printf("%.3g to the power %d is %.5g\n", x, exp, xpow); printf("Enter next pair of numbers or q to quit.\n"); } printf("Hope you enjoyed this power trip -- bye!\n"); return 0; } double power(double n, int p) { double pow = 1; if (p >= 1) { // 只要大于等于1就进去递归 pow = n * power(n, p - 1); } if (p < 0) { pow = 1/n * power(n, p + 1); } if (n == 0 && p == 0) { printf("0的0次方未定义,默认返回为1\n"); } return pow; }
第十题
#include <stdio.h> void to_binary(unsigned long,int); int main(void) { unsigned long number; printf("Enter an integer (q to quit): \n"); while (scanf("%lu", &number) == 1) { printf("Binary equivalent: "); to_binary(number, 8); putchar('\n'); printf("Enter an integer (q to quit):\n"); } printf("Done.\n"); return 0; } void to_binary(unsigned long n,int m) { int r; r = n % m; // 取余数,最后一个数字,先进后出 if (n >= m) { to_binary(n / m, m); } printf("%d", r); return; }
题目我改成了写死了的8进制,确实递归是我的若项,对照书本才写出的代码。
第十一题
#include <stdio.h> unsigned long long Fibonacci(int); int main(void) { int number; unsigned long long res; printf("Enter an fibonacci number (q to quit): \n"); while (scanf("%d", &number) == 1) { res = Fibonacci(number); printf("The fibonacci resule is %llu.\n", res); } printf("Done.\n"); return 0; } unsigned long long Fibonacci(int n) { long i, n1 = 1, n2 = 1; unsigned long long n3 = 1; if (n > 2) { n -= 2; for (i = 1; i <= n; i++) { n3 = n1 + n2; n1 = n2; n2 = n3; } } return n3; }
第10章 数组和指针
10.1 数组
数组在声明的时候可以用过[]实现
比如
int main(void) { float candy[365]; char code[12]; int states[50]; }
10.1.1 初始化数组
只储存单个知道变量也称为标量变量(scalar variable)
C使新的语法来初始化数组
int main(void) { int powers[8] = {1,2,4,8,16,32,64} }
示例代码
#include <stdio.h> #define MONTHS 12 int main(void) { int days[MONTHS] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 ,31}; int index; for (index = 0; index < MONTHS; index++) { printf("Mnnth %2d has %2d days.\n", index + 1, days[index]); } return 0; }
初始化数失败的案例
#include <stdio.h> #define SIZE 4 int main(void) { int no_data[SIZE]; int i; printf("%2s%14s\n", "i", "nodata[i]"); for (i = 0; i < SIZE; i++) { printf("%2d%14d\n", i, no_data[i]); } return 0; }
在我的mac下,用xcode直接跑出来的结果
i nodata[i]
0 0
1 0
2 0
3 0
使用数组前必须先初始化它。
使用部分初始化数组的情况
#include <stdio.h> #define SIZE 4 int main(void) { int no_data[SIZE] = {23,234}; int i; printf("%2s%14s\n", "i", "nodata[i]"); for (i = 0; i < SIZE; i++) { printf("%2d%14d\n", i, no_data[i]); } return 0; }
输出
i nodata[i]
0 23
1 234
2 0
3 0
当初始化列表的值少于数组元素个数时,编译器会把剩余的元素都初始化为0.也就是说,如果不初始数组,数组元素和未初始化的普通变量一样,其中储存的都是垃圾值,但是,如果部分初始化数组,剩余的元素就会被初始化为0。
我们也可以省略方括号中的数字,让编译器自动匹配数组大小和初始化列表中的项数。
#include <stdio.h> int main(void) { int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 ,31}; int index; for (index = 0; index < sizeof(days) / sizeof(days[0]); index++) { printf("Mnnth %2d has %2d days.\n", index + 1, days[index]); } return 0; }
10.1.2 指定初始化器(c99)
C99增加了一个新特性:指定初始化器(designated initialize)。利用该特性可以初始化指定的数组元素。
C99规定,可以在初始化列表中使用带方括号的下标指名待初始化的元素:
int arr[6] = {[5] = 212}; // 把arr[5]初始化为212
对于一般的初始化,在初始化一个元素后,未初始化的元素都会被设置为0。
示例代码
#include <stdio.h> #define MONTHS 12 int main(void) { int days[MONTHS] = {31, 28, [4]=31, 30, 31, [1]=29}; int index; for (index = 0; index < MONTHS; index++) { printf("Mnnth %2d has %2d days.\n", index + 1, days[index]); } return 0; }
输出
Mnnth 1 has 31 days.
Mnnth 2 has 29 days.
Mnnth 3 has 0 days.
Mnnth 4 has 0 days.
Mnnth 5 has 31 days.
Mnnth 6 has 30 days.
Mnnth 7 has 31 days.
Mnnth 8 has 0 days.
Mnnth 9 has 0 days.
Mnnth 10 has 0 days.
Mnnth 11 has 0 days.
Mnnth 12 has 0 days.
Program ended with exit code: 0
示例说明了第一,如果指定初始化器后面的有更多的值,如该例的初始化列表中的片段:[4] = 31,30,31,那么后面这些值将被用于初始化指定元素后面的元素。
第二,如果两次初始化指定的元素,那么最后的初始化将会取代之前的初始化。
10.1.3 给数组元素赋值
只能通过下标的方式给数组赋值,另外都是不允许的。
10.1.4 数组边界
这会是一个C语言很有意思的特性,可以允许你下标越界操作
#include <stdio.h> #define SIZE 4 int main(void) { int value1 = 44; int arr[SIZE]; int value2 = 88; int i; printf("value1 = %d, value2 = %d\n", value1, value2); for (i = -1; i <= SIZE; i++) { arr[i] = 2 * i + 1; } for (i = -1; i < 7; i++) { printf("%2d %d\n", i , arr[i]); } printf("value1 = %d, value2 = %d\n", value1, value2); printf("address of arr[-1]: %p\n", &arr[-1]); printf("address of arr[4]: %p\n", &arr[4]); printf("address of value1: %p\n", &value1); printf("address of value2: %p\n", &value2); return 0; }
输出
value1 = 44, value2 = 88
-1 -1
0 1
1 3
2 5
3 7
4 9
5 32766
6 1542127843
value1 = 44, value2 = 88
address of arr[-1]: 0x7ffeefbff46c
address of arr[4]: 0x7ffeefbff480
address of value1: 0x7ffeefbff468
address of value2: 0x7ffeefbff464
Program ended with exit code: 0
我的实际操作与书中有所不同,我这里没有改变value1与value2的值。但越界操作还是实现了。
这主要归功与C信任程序员的原则。不检查边界,C程序可以运行更快。
10.1.5指定数组的大小
在C99标准之前,声明数组时,只能在方括号使用整数常量表达式。所谓的整数常量表达式,是由整形常量构成的表达式。
sizeof表达式被视为整数常量,另外表达式的值必须大于0。
C90标准支持一下命令方式,叫变长数组(variable-length array)
float a8[n]; float a9[m];
现在我还不是很了解具体用途,待后面学习
10.2 多维数组
书中介绍了二维数组,多维数组的理解就时数组里面的元素还是数组,示例代码如下
#include <stdio.h> #define MONTHS 12 #define YEARS 5 int main(void) { const float rain[YEARS][MONTHS] = { {4.3, 4.3, 4.3, 3.0, 2.0, 1.2, 0.2, 0.2, 0.4, 2.4, 3.5, 6.6}, {8.5, 8.2, 1.2, 1.6, 2.4, 0.0, 5.2, 0.9, 0.3, 0.9, 1.4, 7.3}, {9.1, 8.5, 6.7, 4.3, 2.1, 0.8, 0.2, 0.2, 1.1, 2.3, 6.1, 8.4}, {7.2, 9.9, 8.4, 3.3, 1.2, 0.8, 0.4, 0.0, 0.6, 1.7, 4.3, 6.2}, {7.6, 5.6, 3.8, 2.8, 3.8, 0.2, 0.0, 0.0, 0.0, 1.3, 2.6, 5.2} }; int year, month; float subtot, total; printf("YEAR PAINFALL (inches)\n"); for (year = 0, total = 0; year < YEARS; year++) { for (month = 0, subtot = 0; month < MONTHS; month++) { subtot += rain[year][month]; } printf("%5d %15.1f\n", 2010 + year, subtot); total += subtot; } printf("\nThe yearly average is %.1f inches.\n\n", total / YEARS); printf("MONTHLY AVERAGES:\n\n"); printf(" Jan Feb Mar Apr May Jun Jul Aug Sep Oct "); printf(" Nov Dec\n"); for (month = 0; month < MONTHS; month++) { for (year = 0, subtot = 0; year < YEARS; year++) { subtot += rain[year][month]; } printf("%4.1f ",subtot / YEARS); // 每个月的平均降水量 } printf("\n"); return 0; }
输出
YEAR PAINFALL (inches)
2010 32.4
2011 37.9
2012 49.8
2013 44.0
2014 32.9
The yearly average is 39.4 inches.
MONTHLY AVERAGES:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
7.3 7.3 4.9 3.0 2.3 0.6 1.2 0.3 0.5 1.7 3.6 6.7
Program ended with exit code: 0
10.2.1初始化二维数组
一种时按照前面代码写的,每组少或者多元素不会影响其它小组,还有一种就是只有一个{},但元素少的时候会影响最后那些数组。
10.2.2 其它所谓数组
略
10.3 指针和数组
数组名是数首元素的地址
示例代码
#include <stdio.h> #define SIZE 4 int main(void) { short datas[SIZE]; short * pti; short index; double bills[SIZE]; double * ptf; pti = datas; // 把数组地址给指针 ptf = bills; printf("%23s %15s\n","short", "doubule"); for (index = 0; index < SIZE; index++) { printf("pointers + %d: %10p %10p.\n", index, pti + index, ptf + index); } return 0; }
输出
short doubule
pointers + 0: 0x7ffeefbff480 0x7ffeefbff460.
pointers + 1: 0x7ffeefbff482 0x7ffeefbff468.
pointers + 2: 0x7ffeefbff484 0x7ffeefbff470.
pointers + 3: 0x7ffeefbff486 0x7ffeefbff478.
Program ended with exit code: 0
在C中,指针加1指的是增加一个存储单元。对数组而言,这意味者加1后的地址时下一个元素的地址,而不是下一个字节的地址。
指针的值时它所指向对象的地址。
在指针前面使用*运算符可以得到该指针所指向对象的值。
指针加1,指针的值递增它所指向类型的大小(以字节为单位)
dates + 2 == &dates[2] // 相同的地址 *(dates + 2) == dates[2] // 相同的值
实在代码,通过*地址的方式取值
#include <stdio.h> int main(void) { int days[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30 ,31}; int index; for (index = 0; index < sizeof(days) / sizeof(days[0]); index++) { printf("Mnnth %2d has %2d days.\n", index + 1, *(days + index)); // 这里用*的方式取地址值 } return 0; }
10.4 函数、数组和指针
书中介绍了如何在函数中定义形参,传入指针的时候可以用过 int * ar的方式,也可以通过int ar[]来实现,效果都相同的。
理解ar[1 ] ==*(at + 1)
示例代码
#include <stdio.h> #define SIZE 10 int sum(int [], int n); int main(void) { int marbles[SIZE] = {20 , 10, 5, 39, 4, 16, 19, 26, 31, 20}; long answer; // printf("%zd\n", sizeof marbles); answer = sum(marbles, SIZE); printf("The total number of marbles id %ld.\n", answer); printf("the size of merbles is %zd bytes.\n", sizeof(marbles)); return 0; } int sum(int * ar, int n) { int i; int total = 0; for (i = 0; i < n; i++) { total += ar[i]; } printf("The size of ar %zd bytes.\n",sizeof(ar)); // 指针数据对象的大小 return total; }
The size of ar 8 bytes.
The total number of marbles id 190.
the size of merbles is 40 bytes.
数值在定义函数中,我用int ar[]的方式来对形参进行设置,但由警告。
10.4.1 使用指针形参。
示例代码,直接用指针形参时变量操作。
#include <stdio.h> #define SIZE 10 int sum(int [], int []); // 与 int * 效果相同 int main(void) { int marbles[SIZE] = {20 , 10, 5, 39, 4, 16, 19, 26, 31, 20}; long answer; // printf("%zd\n", sizeof marbles); answer = sum(marbles, marbles + SIZE); printf("The total number of marbles id %ld.\n", answer); return 0; } int sum(int * start, int * end) // 用ar[]会提示警告 { int total = 0; while (start < end) { total += *start; start++; } return total; }
这个函数在操作的时候越界了,越界了一个位置。
但C保证在数组分配空间时,指向数组后面的第一个位置的指针仍是有效的指针[简单来说,就是还可以再超一位]
求和的代码还可以简化为
while (start < end) { total += *start++; }
但最好应该写成这样
while (start < end) { total += *(start++); }
示例清单展示了* 与 ++ 的优先级示例。
#include <stdio.h> int data[2] = { 100, 200}; int moredata[2] = { 300, 400}; int main(void) { int * p1, * p2, * p3; p1 = p2 = data; // 指针赋值 p3 = moredata; printf(" *p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3); printf("*p1++ = %d, *++p2 = %d (*p3)++ = %d\n", *p1++, *++p2, (*p3)++); printf(" *p1 = %d, *p2 = %d, *p3 = %d\n", *p1, *p2, *p3); return 0; }
输出
*p1 = 100, *p2 = 100, *p3 = 300
*p1++ = 100, *++p2 = 200 (*p3)++ = 300
*p1 = 200, *p2 = 200, *p3 = 301
Program ended with exit code: 0
10.4.2 指针表示法和数组表示法
对于C语言,ar[i]和*(ar+i)这两个表达式是等价的。
10.5 指针操作
示例代码
#include <stdio.h> int main() { int urn[5] = { 100, 200, 300, 400, 500}; int * ptr1, * ptr2, * ptr3; // 指针赋值 ptr1 = urn; ptr2 = &urn[2]; // 出现了指针的指针,指针变量也有自己的地址与值,也用&取地址 printf("pointer value, dereferenced pointer, pointer address:\n"); printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1); // 指针加法 ptr3 = ptr1 + 4; // 16进制的表示,4个位置刚好可以进位,所以这个地址刚好在十位数上面进位。 printf("\n adding an int to a pointer:\n"); printf("ptr1 + 4 = %p, *(ptr1 + 4) = %d\n", ptr1 + 4, *(ptr1 + 4)); ptr1++; // 递增指针 printf("\nvalues after ptr1++:\n"); // 指针的指针地址不会变,因为指针对于它来说就是一个变量 printf("ptr1 = %p, *ptr1 = %d, &ptr1 = %p\n",ptr1, *ptr1, &ptr1); ptr2--; printf("\nvalues after --ptr2:\n"); printf("ptr2 = %p, *ptr2 = %d, &ptr2 = %p\n",ptr2, *ptr2, &ptr2); --ptr1; ++ptr2; printf("\nPointers reset to origianl values:\n"); printf("ptr1 = %p, ptr2 = %p\n", ptr1, ptr2); // 一个指针减去另一个指针,结果代表两个元素的距离。 printf("\nsubtracting one pointer from another:\n"); printf("ptr2 = %p, ptr1 = %p, ptr2 - ptr1 = %td\n",ptr2, ptr1, ptr2 - ptr1); // 一个指针减去一个整数 printf("\nsubtracting an int from a pointer:\n"); printf("ptr3 = %p, ptr3 -2 = %p\n", ptr3, ptr3 -2); return 0; }
输出
pointer value, dereferenced pointer, pointer address:
ptr1 = 0x7ffeefbff470, *ptr1 = 100, &ptr1 = 0x7ffeefbff460
adding an int to a pointer:
ptr1 + 4 = 0x7ffeefbff480, *(ptr1 + 4) = 500
values after ptr1++:
ptr1 = 0x7ffeefbff474, *ptr1 = 200, &ptr1 = 0x7ffeefbff460
values after --ptr2:
ptr2 = 0x7ffeefbff474, *ptr2 = 200, &ptr2 = 0x7ffeefbff458
Pointers reset to origianl values:
ptr1 = 0x7ffeefbff470, ptr2 = 0x7ffeefbff478
subtracting one pointer from another:
ptr2 = 0x7ffeefbff478, ptr1 = 0x7ffeefbff470, ptr2 - ptr1 = 2
subtracting an int from a pointer:
ptr3 = 0x7ffeefbff480, ptr3 -2 = 0x7ffeefbff478
Program ended with exit code: 0
书中介绍了,初始化的指针不要通过解引来赋值
int * p; *p = 5;
上面这种操作是不对的。创建一个指针时,系统只分配了存储指针本身的内存,并为分配存储数据的内存。因此,在使用指针之前,必须先用已分配的地址初始化它。
10.6 保护数组中的数据
数组在函数的操作中,必须传递指针,因为这样的效率高。如果一个函数按值传递数组,则必须分配足够的空间来存储原数组的副本,然后把原数组所有的数据拷贝到新的数组中。
如果把数组的地址传递给函数,让数组直接处理原数组效率更高。但这个很有可能会改变原数组
10.6.1 对形式参数使用const
通过在原型函数和函数定义中使用const,告知该函数不能修改指向的数组中的内容。
这样使用const并不是要求原数组是常量,而是该函数在处理数组时将其视为常量,不可修改。
示例代码
#include <stdio.h> #define SIZE 5 void show_array(const double [], int); // 定义不修改传入的数组 void mult_array(double [], int, double); int main(void) { double dip[SIZE] = { 20.0, 17.66, 8.2, 15.3, 22.22}; printf("The original dip array:\n"); show_array(dip, SIZE); mult_array(dip, SIZE, 2.5); printf("The dip array after calling mult_array():\n"); show_array(dip, SIZE); return 0; } void show_array(const double ar[], int n) { int i; for (i = 0; i < n; i++) { printf("%8.3f", ar[i]); } putchar('\n'); } void mult_array(double ar[], int n, double mult) { int i; for (i = 0; i < n; i++) { ar[i] *= mult; } }
书中的示例代码非常不错的展示了,修改与不修改的情况下的运行。
The original dip array:
20.000 17.660 8.200 15.300 22.220
The dip array after calling mult_array():
50.000 44.150 20.500 38.250 55.550
Program ended with exit code: 0
10.6.2 const的其它内容
书中介绍了一些const的用法,本人比较深刻的时初始化一个不能指向别处的指针,const写在指针的前面
double rates[5] = {2 ,3, 4, 5, 6} double * const pc = rates pc = &rates[2]; // 不允许 *pc = 1.9 // 没问题修改rates[0]的值
10.7 指针和多维指针
书中详细说明了二维数组的指针解引,很详细。示例代码如下
#include <stdio.h> int main(void) { int zippo[4][2] = { {2, 4}, {6, 8}, {1, 3}, {5, 7}}; printf(" zippo = %p, ziipo + 1 = %p\n", zippo, zippo + 1); printf("zippo[0] = %p, zippo[0] + 1 = %p\n", zippo[0], zippo[2] + 1); printf(" *zippo = %p, *zippo + 1 = %p\n", *zippo, *zippo + 1); printf(" zippo[0][0] = %d\n", zippo[0][0]); printf(" *zippo[0] = %d\n", *zippo[0]); printf(" **zipppo = %d\n", **zippo); printf(" zippo[2][1] = %d\n",zippo[2][1]); printf("*(*(zippo +2) + 1) = %d\n", *(*(zippo +2) + 1)); return 0; }
输出
zippo = 0x7ffeefbff460, ziipo + 1 = 0x7ffeefbff468
zippo[0] = 0x7ffeefbff460, zippo[0] + 1 = 0x7ffeefbff474
*zippo = 0x7ffeefbff460, *zippo + 1 = 0x7ffeefbff464
zippo[0][0] = 2
*zippo[0] = 2
**zipppo = 2
zippo[2][1] = 3
*(*(zippo +2) + 1) = 3
通过输出可以了解到zippo与zippo[0]的关系*zippo = zippo[0],二维数组中各种指针的关系一目了然
10.7.1 指向多维数组的指针
前面的zippo的指针指向的是一个数组,这样指针该如何定义
int (* pz)[2]
说明指向的是一个内含两个int类型值的数组
这个小括号不能少,书中有解释,因为中括号的优先级更高。
示例代码
#include <stdio.h> int main(void) { int zippo[4][2] = { {2, 4}, {6, 8}, {1, 3}, {5, 7}}; int (* pz)[2]; pz = zippo; printf(" pz = %p, pz + 1 = %p\n", pz, pz + 1); printf("pz[0] = %p, pz[0] + 1 = %p\n", pz[0], pz[2] + 1); printf(" *pz = %p, *pz + 1 = %p\n", *pz, *pz + 1); printf(" pz[0][0] = %d\n", pz[0][0]); printf(" *pz[0] = %d\n", *pz[0]); printf(" **zipppo = %d\n", **pz); printf(" pz[2][1] = %d\n",pz[2][1]); printf("*(*(pz +2) + 1) = %d\n", *(*(pz +2) + 1)); printf("%d\n", *(pz + 1)[0]); // 混合使用,通过指针表示法取二维数组第二个元素,通过数组表示取第一个元素。 return 0; }
输出
pz = 0x7ffeefbff460, pz + 1 = 0x7ffeefbff468
pz[0] = 0x7ffeefbff460, pz[0] + 1 = 0x7ffeefbff474
*pz = 0x7ffeefbff460, *pz + 1 = 0x7ffeefbff464
pz[0][0] = 2
*pz[0] = 2
**zipppo = 2
pz[2][1] = 3
*(*(pz +2) + 1) = 3
6
pz代替了zippo的全部功能
10.7.2 指针的兼容性
不同类型的指针不能赋值,类型一定要相等。
书中示例了二级解引,确实看到了指针让人头痛的方面。
10.7.3 函数和多维数组
编写处理二维数组的函数,要正确的理解写出声明函数的形参。
可以通过前面初始化类型的写法
int (* pr)[2]
也可以
void somefunction(int pt[][4])
示例代码
#include <stdio.h> #define ROWS 3 #define COLS 4 void sum_rows(int ar[][COLS], int rows); // 待参数名 void sum_cols(int [][COLS], int); // 不带参数名 int sum2d(int (* ar)[COLS], int rows); int main(void) { int junk[ROWS][COLS] = { {2, 4, 6, 8}, {3, 5, 7, 9}, {12, 10 ,8 ,6}, }; sum_rows(junk, ROWS); sum_cols(junk, ROWS); printf("Sum of all elemets = %d\n", sum2d(junk, ROWS)); return 0; } void sum_rows(int ar[][COLS], int rows) // 每一行求和 { int r; int c; int tot; for (r= 0; r < rows; r++) { tot = 0; for (c = 0; c < COLS; c++) { tot += ar[r][c]; } printf("row %d: sum = %d\n", r, tot); } } void sum_cols(int ar[][COLS], int rows) { int r; int c; int tot; for (c = 0; c < COLS; c++) { tot = 0; for (r = 0; r < rows; r++) { tot += ar[r][c]; } printf("col %d: sum = %d\n",c, tot); } } int sum2d(int ar[][COLS], int rows) { int r; int c; int tot = 0; for (c = 0; c < COLS; c++) for (r = 0; r < rows; r++) { tot += ar[r][c]; } return tot; }
输出
row 0: sum = 20
row 1: sum = 24
row 2: sum = 36
col 0: sum = 17
col 1: sum = 19
col 2: sum = 21
col 3: sum = 23
Sum of all elemets = 80
Program ended with exit code: 0
定义数组的时候,COLS与ROWS都用了常量。在defina的时候定义。
10.8变长数组(VLA)
对于数组的定义,前面应该讲过必须通过defina定义常量,但c99新增了变长数组(variable-length array),允许使用变量表示数组的维度。
比如
#include <stdio.h> int main(void) { int quartes = 4; int regions = 5; double sales[regions][quartes]; return 0; }
声明一个二维变长数组函数
int sum(int rows, int cols, int ar[rows][cols]);
C99与C11标准规定,可以省略形参名,那只能通过星号来代替省略的维度
int sum2d(int, int, int ar[*][*]);
但函数的定义,头部还是跟第一种声明一样。
一个以变长数组作为形参的函数既可以处理传统C数组,也可处理变长数组。
示例代码
#include <stdio.h> #define ROWS 3 #define COLS 4 int sum2d(int rows, int cols, int ar[rows][cols]); int main(void) { int i, j; int rs =3; int cs = 10; int junk[ROWS][COLS] = { {2, 4, 6, 8}, {3, 5, 7, 9}, {12, 10, 8, 6} }; int morejunk[ROWS-1][COLS+2] = { {20, 30, 40, 50, 60, 70}, {5, 6, 7, 8, 9, 10} }; int varr[rs][cs]; for (i = 0 ; i < rs; i++) { for (j = 0; j < cs; j++) { varr[i][j] = i * j + j; } } printf("3x5 array\n"); printf("Sum of all elements = %d\n", sum2d(ROWS, COLS, junk)); printf("2x6 array\n"); printf("Sum of all elements = %d\n", sum2d(ROWS-1, COLS+2, morejunk)); printf("3x10 array\n"); printf("Sum of all elements = %d\n", sum2d(rs, cs, varr)); return 0; } int sum2d(int rows, int cols, int ar[rows][cols]){ int r, c; int tot = 0; for (r = 0; r < rows; r++) { for (c = 0; c < cols; c++) { tot += ar[r][c]; } } return tot; }
输出
3x5 array
Sum of all elements = 80
2x6 array
Sum of all elements = 315
3x10 array
Sum of all elements = 270
Program ended with exit code: 0
10.9 复合字面量
主要意思就是讲了可以定义一个数组的常量,用户赋值或者在函数中可以直接当实参使用。
定义的方式跟定义数组的少了一个变量名一样
(int [2]){3, 4}
上示例代码
#include <stdio.h> #define COLS 4 int sum2d(const int ar[][COLS], int rows); int sum(const int ar[], int n); int main(void) { int total1, total2, total3; int * pt1; int(*pt2)[COLS]; // 指向二维数组的指针 pt1 = (int[]){ 10, 20}; // 通过数组常量赋值 pt2 = (int[2][COLS]){ {1, 2, 3, -9}, {4, 5, 6, -8}}; total1 = sum(pt1, 2); total2 = sum2d(pt2, 2); total3 = sum((int[]){4, 4, 4, 5, 5, 5}, 6); printf("total1 = %d\n", total1); printf("total2 = %d\n", total2); printf("total3 = %d\n", total3); return 0; } int sum(const int ar[], int n){ int i; int total = 0; for (i = 0; i < n; i++) { total += ar[i]; } return total; } int sum2d(const int ar[][COLS], int n){ int i, j; int total = 0; for (i = 0; i < n; i++) { for (j = 0; j < COLS; j++) { total += ar[i][j]; } } return total; }
输出
total1 = 30
total2 = 4
total3 = 27
10.10 关键概念
略
10.11 本章小结
略
10.12复习题
第一题
8 8
4 4
0 0
2 2
第二题
4个元素
第三题
ref的地址就是元类类第一个元素的地址,ren+1就是数组内第二个元素的指针地址,++ref与ref+1相等的效果。
++ref不是有效的表达式,因为ref是一个常量,不是变量。
第四题
#include <stdio.h> int main(void) { int * ptr; int * pr; int torf[2][2] = {12, 14, 16}; // 不足的元素会自动补全 int fort[2][2] = {{12}, {14, 16}}; // 不足的元素小组自动补全 ptr = torf[0]; pr = fort[0]; printf("%d, %d \n", *ptr, *(ptr + 2)); printf("%d, %d \n", *pr, *(pr + 2)); return 0; }
输出
12, 16
12, 14
Program ended with exit code: 0
第五题
12 16
12 14
第六题
gird[22] + 56
*(gird + 22) gird[22]
gird gird[0] *gird
第七题
int digits[10]
float rates[6]
int mat[3][5]
int (*psa)[20] int* psa[20]
char (* pstr) [20]
第八题
int ar[6] = {1, 2, 4, 8, 16, 32};
int a[]= {[2] = 4};
int ar[100] = {ar[99] = -1}
int ar[100] = {[5] = 101, [10] = 101,101,101,[3] = 101}
第九题
范围为0 到 9
第十题
a 有效
b 有效 无效rootbeer是指针
c 无效 [一个指针,一个整形]
d 无效 [一个指针,不能进行%f格式化输出,要用%p]
e 有效
f 有效 无效,不能用数组进行赋值
g 无效 [一个float的值,不能赋值给指针]
h 有效
第十一题
int ar[800][600]
第十二题
传统
#include <stdio.h> void func(double ar[], int row); int main() { return 0; } void func(double ar[], int row) { }
变长
#include <stdio.h> void func(int row, double ar[]); int main() { return 0; } void func(int row, double ar[]) { }
clops
传统
#include <stdio.h> void func(short clops[][30], int row); int main() { return 0; } void func(short clops[][30], int row) { }
变长
#include <stdio.h> void func(int row, int column, short clops[*][*]); int main() { return 0; } void func(int row, int column, short clops[row][column]) { }
shots
标准
#include <stdio.h> void func(long shots[][10][15], int row); int main() { return 0; } void func(long shots[][10][15], int row) { }
变长
#include <stdio.h> void func(int row,int columns, int y, long shots[*][*][*]); int main() { return 0; } void func(int row,int columns, int y, long shots[row][columns][y]) { }
第十三题
#include <stdio.h> void show(const double ar[4], int n); void show2(const double ar2[][3], int n); int main() { show((double [4]){8. ,3. ,9. ,2.}, 4); show2((double [2][3]){{8,3 ,9,},{5,4,1}}, 2); return 0; } void show(const double ar[], int n) { float res = .0; int i; for (i = 0; i < n; i++) { res += ar[i]; } printf("The sum result is %f.\n", res); } void show2(const double ar2[][3], int n) { float res = .0; int i, j; for (i = 0; i < n; i++) { for (j = 0; j < 3; j++) { res += ar2[i][j]; } } printf("The sum2 result is %f.\n", res); }