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);
}

 

posted @ 2020-12-20 23:59  就是想学习  阅读(140)  评论(0编辑  收藏  举报