C学习笔记

C Primer Plus (第6版) 中文版

第1章 初始C语言

编译器:将高级语言翻译成数字指令码(机器语言)

编译gcc 源代码.c -o 可执行代码.exe

.c:表示源代码文件

.exe:可执行的代码

getchar():读取一个字符


第2章 C语言概述

include:具有共享作用

stdio.h:C编译器软件包的标准部分,标准的输入输出头文件

#include:这行代码具有预处理(preprocessing)作用,称为头文件

注释风格// 是C99新增的一种风格注释,普遍用于C++和Java

#include <stdio.h> /*头文件*/
/**
 * @brief main函数,C语言首先调用的函数
 * 
 * @return float main函数返回一个float
 */
float main (void) {  //不符合标准
    printf("I am");  /*读取一个Enter键*/
    return 0;
}

void:参数是空

int:返回值为整数

int main(void)  //固定标准形式
int main()      //C90标准可接受,C99、C11标准不接受,标准越来越严格

2.1 函数定义-多个

  1. 声明函数(函数原型)
  2. 定义函数

二者的参数和返回值保持一致

#include <stdio.h>
int buffer(void);  /*声明buffer函数*/

int main(void)
{
 printf(buffer() + "Yes, Let us jump up right in .");/*使用buffer函数*/
 return 0;
}

int buffer(void) /*定义buffer函数定义,必须与声明的函数一致*/
{
    printf("Are you ready ?\n");
    return 0;
}

注意

#define DAYS_PER_YEAR 365 (✔)

#define DAYS_PER_YEAR = 365 (❌)


第3章 数据和C

%.2f:输出小数位后两位

%lf:打印double型数据

%ld:打印long型数据

%hd:打印short型数据

%u:打印unsigned型

unsigned:非负

_Bool:布尔类型(整数)

_Complex:复数

_Imaginary:虚数

3.1 存储方式-浮点数

符号位小数部分指数部分
+.31415921

结果:3.141592

3.2 表示方式-进制

八进制(octal):用0开头

十六进制(hexadecimal):以 0X0x 开头

3.3 显示方式-进制

  • 将数以xx进制进行显示

    • 十进制(decimal ):%d
    • 八进制(octal):%o (哦~)
    • 十六进制(hexadecimal):%x
  • 前缀进制数显示

    • 八进制(octal):%#o
    • 十六进制(hexadecimal):%#x %#X

代码

#include <stdio.h>

int main (void)
{
    int x = 100;  /*可换成0100、0x100进行演示*/

    printf("进制数显示\n");
    printf("\tdec = %d; octal = %o; hex = %x\n", x, x, x);
    
    printf("带前缀进制数显示\n");
    printf("\tdec = %d; octal = %#o; hex = %#x\n", x, x, x);
    return 0;
}

结果

进制数显示
dec = 100; octal = 144; hex = 64
带前缀进制数显示
dec = 100; octal = 0144; hex = 0x64

3.4 _Bool类型

​ C99标准添加了_Bool类型,C语言用1表示true,用0表示false;

所以_Bool实际上是一种占1位的整数类型(1字节8位)

  • 可移植类型
    • stdint.h:编译器会把 intlong 替换成与当前系统匹配的类型
    • inttypes.h:解决 int32_t 打印输出时用 %d 还是 %ld 的规定不同的问题

3.5 浮点型

float:至少能表示6位有效数字,通常占32位,其中8位用于表示符号和指数,24位表示非指数部分,也叫做尾数有效数

double:至少能表示10位有效数字,通常64位多出来的32位表示非指数部分,增加了有效数字位数(即提高了精度),还减少了舍入误差

3.6 上下溢

代码

#include <stdio.h>  /*新增了两个头文件*/
#include <float.h>  /*浮点数的上下溢==>FLT_MAX、FLT_MIN*/
#include <limits.h> /*整数上溢==>INT_MAX*/

/*整型上溢+浮点数上溢和下溢*/
int main (void)
{
    /*1. 整数上溢*/
    int big_int = 2147483647;
    printf("The big int data is %d\n", ++big_int);

    /*2. 浮点数上溢*/
    float big_float = 3.4e38;
    printf("The big float data is %f\n", big_float * 10);

    /*3. 浮点数下溢*/
    float small_float = 10.0 / 3;
    printf("The small float data is %f\n", small_float);

    printf("The MAX int data is %ld\n", INT_MAX);
    printf("The MAX int data is %ld\n", INT_MIN);
    printf("The MAX float data is %f\n", FLT_MAX);
    printf("The MIN float data is %f\n", FLT_MIN);
    return 0;
}

结果

The big int data is -2147483648
The big float data is 1.#INF00
The small float data is 3.333333
The MAX int data is 2147483647
The MAX int data is -2147483648
The MAX float data is 340282346638528860000000000000000000000.000000
The MIN float data is 0.000000


第4章 字符串和格式化输入/输出

4.1 strlen()、sizeof()

  • strlen()函数

    • 读取字符串\0结尾,字符串大小不包括\0
    • char型数组大小计算同上
  • sizeof()函数

    • 大小包含字符串后的**\0**结尾符
    • char name[40] 函数读取char型数组字节大小
  • int values[5]

    • sizeof(values)===>返回 5x4 = 20
    • sizeof(values) / sizeof(values[0]) ==> 5

不适用于二维数组遍历

代码

#include <stdio.h>
#include <string.h>  /*strlen函数的头文件*/
#define NAME "廖述幸"

int main (void)
{
    char name[40] = NAME;  /*C语言中没有String类型,字符串存储通常以\0结尾,不计算\0*/
    printf("What is your name?\n My name is %s\n", name);

    printf("How about name's length?\n It is %d\n", strlen(name));  /*遇到字符串末/0结束===>字符串大小*/
    printf("How about name's size?\n It is %d\n", sizeof(name));  /*得到===>char数组大小*/
    return 0;
}

结果

What is your name?
My name is 廖述幸
How about name’s length?
It is 6
How about name’s size?
It is 40

4.2 字符和字符串

'x’是一个字符"x"是一个字符串
存储形式xX\0

4.3 const 限定符

来源:C90标准新增const关键字

作用:限定一个变量只读

优势:比#define更为灵活

const int MONTHS = 12;  /*正确的定义,MONTHS在程序中不能修改*/

#define和const的区别(参考链接)

4.4 转换说明-字段宽度

转换说明:%s %d %f

%4d:如果打印的数字没有4位,会用空格补全

%5.2f字段宽度5位,小数点后2位

%10.2s字段宽度10位,有效位数2位,右对齐

%-10.2s字段宽度10位,有效位数2位,-表示左对齐

%-03.2d:字段宽度3位,有效位数2位,用0代替空格填充字段宽度,超出字段宽度,则不需要填充,直接进行显示

p72

代码

#include <stdio.h>

int main (void)
{
    char num[40] = "廖述幸";

    printf("|%10.2s|\n", num);  /*可以换成 %10.4s 进行演示,通常中文占2字节*/
    printf("|%-10.2s|", num);
    return 0;
}

结果

| 廖|
|廖 |

代码

#include <stdio.h>
#define NAME "廖述幸"
#define ADDRESS "湖南省武冈市xxx镇xx村xx组"
#define PHONE_NUMBER "166xxxx7152"

int main (void)
{
    printf("%-20s %-40s %-20s\n", "姓名", "住址", "手机号码");
    printf("%-20s %-40s %-20s\n", NAME, ADDRESS, PHONE_NUMBER);
    return 0;
}

结果

姓名                 住址                                  手机号码
廖述幸               湖南省武冈市xxx镇xx村xx组                166xxxx7152
  • 输出整齐美观
// 1、正常输出
printf("%d\t%d\t%d\n", 312, 2344, 136);
printf("%d\t%d\t%d\n", 400, 234, 3412);

// 2、使用固定字段宽度进行输出,字符宽度为6,不足空格补齐,默认右对齐
printf("%6d\t%6d\t%6d\n", 312, 2344, 136);
printf("%6d\t%6d\t%6d\n", 400, 234, 3412);

---输出结果比较---
312	2344	136
400	234	3412
   312	  2344	   136
   400	   234	  3412

4.5 精度自由

float salary = 123.00;
int width, precision;
printf("Enter a width and a precision:\n");
scanf("%d %d", &width, &precision);
printf("%*.*f", width, precision, salary); // *表示字段宽度,由键盘输入进行精度控制

%s和scanf函数的输入

char name[30];
printf("Input one name:");
scanf("%s", name); // 只会读取部分,遇到空白处停止写入
printf("%s", name);
return 0;
---输出结果---
Input one name: carter I love you
carter

4.6 Scanf-字符数组

char型数组:使用scanf输入时,前面不需要==&==

Scanf并不是最常用的输入!!!

  • %s:不接收含空白的字符串,空格处终止
  • %c:可接收空格字符,仅读一个字符

原因:遇到第一个空白,scanf认为工作完成,后续数据不再写入当前变量,只保存在输入缓冲区中!!!

注意

scanf("%c", &ch);  /*从输入中第一个字符开始读取*/
scanf(" %c", &ch); /*从输入中第一个非空白字符开始读取*/

代码

#include <stdio.h>

int main (void)
{
    int age;
    float assets;
    char pet[30];  /*这是一个字符串*/

    printf("Please input your value:");
    scanf("%d", &age);
    scanf("%f", &assets);
    scanf("%s", pet);  /*字符数组不需要&*/


    printf("age = %d\n", age);
    printf("assets = %f\n", assets);
    printf("pet = %s\n", pet);

    char c;
    printf("输入一个字符:\n");
    
    getchar();  /*必须来一个这个不然会跳过下一个输入*/
    
    scanf("%c", &c);  /*需要&符号,可输入空白字符*/
    printf("[%c]", c);  /*可以接收空白*/
}

结果

Please input your value:12 11.90 hello world~
age = 12
assets = 11.900000
pet = hello

输入一个字符:

[ ]

注意:hello world===>仅读取hello

4.7 %*d-修饰符灵活运用

  • 通过scanf输入,自定义字段宽度

代码

#include <stdio.h>

int main (void)
{

    int width, number;  /*width提供字段宽度,number是待打印的数字*/
    printf("Please input the String's width:_____\b\b\b");
    scanf("%d", &width);

    printf("\nThe number is ");
    getchar();  /*防止闪过,往往在scanf上添加*/
    scanf("%d", &number);

    printf("格式化输出:[%-*d]", width, number);
    getchar();/*防止.exe可执行文件闪退问题,必须得来两个,无情*/
    getchar();/*防止.exe可执行文件闪退问题 缓存区有字符在,用getchar()清空缓存区,最好使用循序*/
    
    return 0;
}

结果

Please input the String’s width:_ 9__

The number is 10
格式化输出:[10 ]

4.8 跳过String,取之后Int类型

代码

#include <stdio.h>

/*输入catch 22 跳过字符串catch读取value=22*/
int main (void)
{
    int value;
    printf("Input String is ");
    scanf("%*s %d", &value);  /*忽略字符串,取后面的Int型数据*/

    printf("value = %d", value);
    return 0;
}

演示

Input String is value 11
value = 11

4.9 String字符长度计算

  • printf函数:注意不换行
  • strlen函数

代码

#include <stdio.h>
#include <string.h>

/*
获取字符串大小
①使用printf函数
②使用strlen函数
*/
int main (void)
{
    char name[20];
    printf("Input Your name:");
    scanf("%s", name);

    int lenthByPrintf, lenthByStrlen;

    lenthByPrintf = printf("%s", name);  /*Printf返回值记录了字符数量===>注意不要换行*/
    lenthByStrlen = strlen(name);  /*Strlen函数获取字符数量,需添加头文件string.h*/

    printf("\nlenthByPrintf is %d, lenthByStrlen is %d", lenthByPrintf, lenthByStrlen);
    return 0;
}

演示

Input Your name:carter
carter
lenthByPrintf is 6, lenthByStrlen is 6

注意

lenthByPrintf = printf("%s", name);     ===> len = 6
lenthByPrintf = printf("%s\n", name);   ===> len = 7 (换行符也被计算)

第5章 运算符、表达式和语句

5.1 除法运算

整数➗整数 = 整数

浮点数➗浮点数 = 浮点数

代码

#include <stdio.h>

/*除法运算*/
int main (void)
{

    int a = 1/3;
    float b = 1/3;
    float c = 1.0/3.0;

    printf("int a = %d\n", a);
    printf("float b = %.2f\n", b);
    printf("float c = %.2f\n", c);
    return 0;
}

演示

int a = 0
float b = 0.00
float c = 0.33

5.2 ++运算

问题

  • x = x + 1 是否等价于 ++x,那x++呢?
  • x必须是可修改的值

常见的错误

#include <stdio.h>
#define b 2

int main (void)
{
    const int a  = 1;
    printf("%d", a++);  /*其中a是常量,不可变*/
    printf("%d", b++);
    
    return 0;
}

结果

编译错误

  • 表达式a、b必须是可修改的值

++a与b++

int number = 1;
printf("number=%d", number--); // 后缀模式->输出 1
printf("number=%d", --number); // 前缀模式->输出 -1
// ++x; <==等价==> x = x + 1;

Question

++num*num
    num*num++
    之间的区别???

sizeof:以字节为单位返回运算对象的大小

  • 返回值类型:size_t(无符号整数类型)

副作用(side effect)

  • 语句的主要目的
printf() \\ 副作用就是显示的信息
int i;
i = 1; \\ 副作用就是将变量i的值修改为1

序列点(sequence)

  • 完整表达式结束
  • 语句中的分号就是序列点的标记

5.3 强制类型转换

int mice = (int) 1.3 / (int) 1.7;

5.4 参数分类

C99标准规定:

  • 实参:argument(有定值的,如5、int a = 1的a)
  • 形参:parameter(int a)

第6章 C控制语句:循环

阿拉伯数字中仅有0表示false

#include <stdio.h>

int main(void)
{
    int n = 3;

    while(n)
        printf("%2d is true!\n", n--);
    
    printf("%2d is false!\n", n);
    return 0;
}

结果如下:
 3 is true!
 2 is true!
 1 is true!
 0 is false! ---仅有0表示false

_Bool类型

  • _Bool类型用于存储int类型
    • 存入的数是int类型,我们**__Bool就会置为1**
    • 存入的非int类型_Bool就会置为0
    • 使用stdbool.h===> bool
#include <stdio.h>

int main(void)
{
    long num;
    long sum = 0L;
    _Bool input_is_good;
    
    printf("please input a number:\n");
    input_is_good = (scanf("%ld", &num) == 1);
    
    while (input_is_good) {
        sum = sum + num;
        printf("Please enter next integer (q to quit) :\n");
        input_is_good = (scanf("%ld", &num) == 1);
    }

    printf("Those integers sum to %ld.\n", sum);
    return 0;
}
======运行结果======
please input a number:
12
Please enter next integer (q to quit) :
12
Please enter next integer (q to quit) :
1.1
Please enter next integer (q to quit) :
Those integers sum to 25.
    
=====结论:输入int值_Bool都将是1,继续迭代!!!其他类型退出。=====

6.1 scanf函数返回值

Scanf()函数

  • 当控制台的输入与%ld进行匹配
    • 匹配,返回值为1
    • 不匹配,返回值为0

6.2 运算符优先级

算数运算符 > 关系运算符 > 赋值运算符

  • 算数运算符 > 关系运算符
    • x > y + 2 ===> x > (y + 2)
  • 关系运算符 > 赋值运算符
    • x = y > 2 ===> x = (y > 2)

6.3 while循环

for (; test; ;)
while (test)	/*二者效果相同*/

第7章 C控制语句:分支和跳转

7.1 打印%符号

  • %%
int main (void)
{
    printf("%%");
    return 0;
}

代码

#include <stdio.h>
#define FORMAT "%s! C is pretty good!\n"
#define PERCENT '%'

int main (void)
{
    printf(FORMAT, FORMAT);
    printf("%c", PERCENT);
    
    return 0;
}

结果

%s! C is pretty good!
! C is pretty good!
%

7.2 getchar()与putchar()

getchar:无参,从输入队列中返回下一个字符

putchar:有参,和printf效果相同,但只能接收单个字符

代码

#include <stdio.h>

int main (void)
{
    char ch;

    printf("Input your name: ");
    scanf("%c", &ch);
    ch = getchar();  /*获取输入的下一个字符*/
    
    printf("ch = [%c]\n", ch);
    return 0;
}

演示

Input your name: carter
ch = [a]

代码

#include <stdio.h>

int main (void)
{
    char ch;
    ch = getchar();
    
    printf("ch = [%c]", ch);
    return 0;
}

演示

q
ch = [q]

联合使用

#include <stdio.h>
#define SPACE ' '

/*getchar与putchar的妙用*/
int main (void)
{
    char ch;
    ch = getchar();  /*获取一个字符*/

    while (ch != '\n')  /*以换行符为一行结束的标志*/
    {
        if (ch == SPACE)
            putchar(ch);  /*当前字符是空格则不改变*/
        else
            putchar(ch + 1);  /*非空格,打印ASCII码加1的字符*/

        ch = getchar();   /*继续获取下一个字符*/
    }
    putchar(ch);  /*打印换行符*/
    return 0;
}

演示

CALL MY NAME.
DBMM NZ OBNF/

7.3 ctype.h系列的字符函数

​ 输入一串字符,仅仅转换所有字母,其它保留原样!

问题:如果通过if条件,列出所有可能性太繁琐

解决方案ctype.h头文件包含一系列专门处理字符的函数

Page156

代码

#include <stdio.h>
#include <ctype.h>  /*C语言中专门处理字符的头文件*/

#define SPACE ' '

int main (void)
{
    char ch = getchar();  /*读取一个字符*/

    while ( ch != '\n')
    {
        if (!isalpha(ch))  /*不是字母就保留原样的条件*/
            putchar(ch);
        else
            putchar(ch + 1);
        
        ch = getchar ();
    }
    putchar(ch);
    return 0;
}

结果

LOOK! It’s a programmer!
MPPL! Ju’t b qsphsbnnfs!

7.4 多重选择 else if

  • 你想象中的 else if
#include <stdio.h>

int main (void)
{
    int a;
    printf("Input a number: ");
    scanf("%d", &a);

    if (a < 0)
        printf("a<0\n");
    else if (a < 10)
        printf("a是个位数\n");
    else if (a < 100)
        printf("a是两位数");
    else
        printf("a > 100");

    return 0;
}
  • 实际中的 else if
#include <stdio.h>

int main (void)
{
    int a;
    printf("Input a number: ");
    scanf("%d", &a);

    if (a < 0)
        printf("a<0\n");
    else
        if (a < 10)
            printf("a是个位数\n");
        else
            if (a < 100)
                printf("a是两位数");
            else
                printf("a > 100");
    return 0;
}

7.5 iso646.h 头文件

C99标准增加了可替换逻辑运算符(&&、||、!)的英文拼写

传统写法iso646.h
&&and
||or
!not

7.6 条件运算符 ?

C语言中唯一的三元运算符

x = (y < 0) ? y : -y
    
如果 y < 0 ===> x = y;
如果 y >= 0 ===> x = -y;

7.7 循环辅助:continue和break

continue:跳过本次迭代的剩余部分,直接进入下次循环

break:终止包含它的循环

goto:基本不使用

《C primer plus (第6版)》 P172

第8章 字符的输入/输出和输入验证

8.1 输入验证

  • 非负整数
long n;
scanf("%ld", &n);
while (n >= 0)
{
    /*表达式*/
    scanf("%ld", &n);
}
  • 整数类型&非负
long n;
while ((scanf("%ld", &n)) == 1 && n >= 0)
{
    scanf("%ld", &n);
}

第9章 函数

9.1 指针数据交换

代码

#include <stdio.h>
void integerChange(int * x, int * y);

int main (void)
{
    int x = 10;
    int y = 11;
    printf("交换前: x = %d y = %d\n", x, y);

    integerChange(&x, &y);
    printf("交换后: x = %d y = %d\n", x, y);

    return 0;
}

/*数据交换*/
void integerChange(int *x, int *y)
{
    int temp;
    temp = *x;
    *x = *y;
    *y = temp;
}

结果

交换前: x = 10 y = 11
交换后: x = 11 y = 10

9.2 函数的声明和定义

三种方法

/*函数声明的三种方法*/
#include <stdio.h>

/*ANSI之前的形式声明函数原型*/
void dibs();  /*第1种:旧式函数声明*/

/*ANSI C形式声明函数原型*/
void sum01 (int a, int b);  /*第2种:标准形式*/
void sum02 (int, int);  /*第3种:省略变量名*/

int main (void)
{
    dibs(2, 3);
    sum01(3, 4);
    sum02(4, 5);

    return 0;
}

/*第1种方法*/
void dibs(x, y)
int x, y;
{
    printf("x + y = %d\n", x+y);
    return;
}

/*第2种方法*/
void sum01 (int a, int b)
{
    printf("a + b = %d\n", a+b);
    return;
}

/*第3种方法*/
void sum02 (int a, int b)
{
    printf("a + b = %d\n", a+b);
    return;
}

结果

x + y = 5
a + b = 7
a + b = 9

终极方法

/*函数声明定义一体化*/
#include <stdio.h>

void sum (int a, int b)
{
    printf("a + b = %d", a + b);
    return;
}

int main (void)
{
    sum(2, 3);
    return 0;
}

结果

a + b = 5


第10章 多维数组

10.1 指针和数组

ar[i] == *(ar+1) == *++ar

根据首地址取值

#include <stdio.h>
void sum (int * firstAddr, int arraysLen);

int main (void)
{
    int arrays[5] = {1, 2, 3, 4, 5};

    int arraySize = sizeof(arrays) / sizeof(arrays[0]);  /*数组大小获取*/
    printf("数组大小:%d\n", arraySize);

    printf("arrays = %#p\n", arrays);
    printf("&arrays[0] = %#p\n", &arrays[0]);  /*二者等价*/

    sum(&arrays[0], arraySize);
    return 0;
}

/**
 * @brief 计算数组内数字总和
 *  firstAddr:首地址
 *  arraysSize: 数组大小
 */
void sum (int * firstAddr, int arraysSize)
{
    int total = 0;

    for (int i = 0; i < arraysSize; i++, firstAddr++)  /*当前地址:firstAdrr 下一个数据地址:firstAdrr + 1*/
    {
        printf("第%d个数\t地址:%#p 值=%d\n", i + 1, firstAddr, *firstAddr);
        total += *firstAddr;
    }
    printf("总和=%d", total);
    return;
}

结果

数组大小:5
arrays = 0X000000000061FE00
&arrays[0] = 0X000000000061FE00
第1个数 地址:0X000000000061FE00 值=1
第2个数 地址:0X000000000061FE04 值=2
第3个数 地址:0X000000000061FE08 值=3
第4个数 地址:0X000000000061FE0C 值=4
第5个数 地址:0X000000000061FE10 值=5
总和=15

10.2 const和指针

注意

int values[3] = {1,2,3};
const int *ptr = values;
ptr++;  /*没有毛病,指向values[1]*/
printf("%d", *ptr); ===>输出2
    
    
const int a = 1;
a++;  /*有毛病,a是不可更改的常量*/

*为准,左值右指针

  • const int * ptr

    • 可修改指针指向的地址,如:ptr++、ptr = &values[2] ✔
    • 不可修改指向的值,如:*ptr = 4 ❌
  • int * const ptr

    • 不可修改指向的地址,如ptr++ ❌
    • 可修改指向的值,如:*ptr = 4 ✔
  • const int * const ptr

    • 不可修改指向的地址,如ptr++ ❌
    • 不可修改指向的值,如:*ptr = 4 ❌

10.3 指针和多维数组

int zipoo[4][2]

zipoo数组名:首元素的地址**&zipoo[0]**

zipoo[0]&zipoo[0] [0]地址

*(zipoo[0])zipoo[0] [0]值

*zipoo&zipoo[0] [0]地址

**zipoozipoo[0] [0]值

10.4 指向多维数组的指针

int (*ptr) [2]:ptr指向一个内含两个int类型值的数组

int * ptr [2]:ptr内含两个指针元素的数组

等价关系

zippo[m][n] == *(*(zippo + m) + n)
pz[m][n] == *(*(pz + m) + n)
int pt[][4];  //一个内含4个类型值的数组指针
int [][4];  //可以省略数组名
int (*pr)[4];  //两者等价

形参声明

int sum (int ar[][], int rows);  //错误声明
int sum (int ar[][4],int rows);  //正确声明
int sum (int ar[3][4], int rows);  //正确声明,3会被编译器自动忽略,同上
int sum (int (*ar)[4], int rows);  //同上

10.5 变长数组

​ C99新增了变长数组,属于C语言的新特性

  • 变长数组:动态分配
  • 普通数组:静态分配

函数声明

int sum (int rows, int cols, int ar[rows][cols]);  /*标准形式,注意先后顺序不可修改*/
int sum (int, int, int ar[*][*]);  /*C99/C11标准规定,可以省略原型中的形参名,但数组必须用✳代替省略的维度*/

10.6 复合字面量

  • 优点:把信息传入函数前不必先创建数组,复合字面量的典型用法
int diva[2] = {10, 20};
(int [2]) {10, 20};  /*匿名函数*/

int sum ((int [2]) {10, 20, 30}, 3);
/*
 此时的匿名函数:
    ①是内含3个int类型值的数组,和数组名类似;
    ②也是该数组首元素的地址;
*/

第11章 字符串和字符串函数

11.1 字符串数组

  • 字符数组:一个装着字符串字面量的副本

  • 字符串:以\0结尾

  • 指针表示法:拥有递增操作++

  • 指针数组

    • 优点:仅显示字符串,效率更高
    • 缺点:字符串字面量不能更改
  • 普通数组

    • 优点:易于更改操作
    • 缺点:消耗内存多

如果要改变字符串或为字符串输入预留空间,不要使用指向字符串字面量的指针

p281

11.2 字符串输入函数

11.2.1 gets()函数

scanf()和转换说明%s只能读取一个单词。gets()函数是读取整行输入,遇到换行符结束。

  • 读取的字符后添加一个空字符,使之成为一个字符串。

  • 不存储换行符

代码

#include <stdio.h>
#define MAX 4

int main (void)
{
    char str [MAX];
    printf("请输入一个字符串:");
    gets(str);
    puts(str);
    return 0;
}

问题

  • 当输入的字符串大小远远超过str的最大存储容量MAX-1,将会出现警告⚠
  • 缓冲区溢出(buffer overflow)
    • 即多余的字符超出指定的目标空间。这些多余的字符只是占用了尚未使用的内存,就不会立即出问题。如果它们擦除了其它程序中的其它数据,就会导致程序异常终止;或者出现其它不安全的情况。⚠
11.2.2 fgets()函数

fgets()函数通过第2个参数限制读入的字符数量来解决缓冲区溢出的问题。

  • 第2个参数n:读入字符的最大数量,最多读取n-1个字符
  • 存储换行符
  • 第3个参数指明要读入的文件。键盘读取(stdin标准输入)
  • fputs()函数:stdout(标准输出)不换行
  • 读取错误:返回NULL

问题

​ 读取到n-1个字符截至,后面的字符仍然存放在缓冲区中,如果不清理缓冲区,下一次会读取遗留在缓冲区的字符!

代码

#include <stdio.h>
#define MAX 10
int main (void)
{
    char p[MAX];
    printf("请输入字符串:");
    fgets(p, MAX, stdin);  /*实际读取字符MAX-1个*/
    
    fputs(p, stdout);  /*不自动添加换行符*/
    puts(p);  /*自动添加换行符*/
    return 0;
}
12
123
1234
12345

问题

fgets()函数会读取换行符,所以如何删除存储在字符串中的换行符呢?

  • 遍历字符串,直到遇到换行符或空字符。
    • 换行符:使用空字符替换
    • 空字符:丢弃输入行的剩余部分

代码

/*删除fgets()函数读取的换行符*/
#include <stdio.h>
#define MAX 10
int main (void)
{
    char words[MAX];
    int i;
    printf("Enter strings(Empty line to quit): ");

    while (fgets(words, MAX, stdin) != NULL && words[0] != '\n')
    {
        i = 0;
        while (words[i] != '\n' && words[i] != '\0')
            i++;

        if (words[i] == '\n')  /*遇到\n符号就把它替换为空字符*/
        {
            printf("第%d次,偶遇换行符...\n", i);
            words[i] = '\0';
        }
        else  /*如果遇到空字符,就丢弃输入行的剩余字符*/
        {
            printf("第%d次,与空字符相遇...\n", i);
            while (getchar() != '\n')  /*读取的字符是\0,字符串结束的标识符,读取完毕,清除缓冲区字符数据*/
                continue;
        }
        puts(words);  /*最后输出读取的字符串*/
    }

    return 0;
}
11.2.3 gets_s()函数

​ 特殊的fgets()函数,只能从标准输入(stdin)中读取数据,不需要第3个参数

  • 丢弃换行符
  • 读取最大字符数量时
    • 把目标数组中首字符设为空字符
    • 读取并丢弃随后的字符—>换行符或文件结尾,然后返回空指针
    • 调用处理函数

11.3 字符串输出函数

11.3.1 puts()函数
  • 自动添加换行符
  • 遇到空字符停止输出
  • 必须保证有空字符,无法正常输出字符数组(不以\0结尾)
  • 参数是 char * int

gets()函数丢弃换行符

代码

char mesg [] = "Everything is ok!";
puts(mesg + 3);  /*注意此时的起始位置是从 r开始打印*/
puts(&mesg[3]); /*效果同上,注意传递的参数类型是指针*/

结果

rything is ok!
rything is ok!
===是不是有点不可思议?===
11.3.2 fputs()函数
  • 参数2:指明写入数据的文件
  • 不会添加换行符

fgets()函数保留换行符

11.3.3 自定义输出函数

代码

/*自定义打印函数1*/
void putx1(const char * words)  /*需要打印的字符串不能变动*/
{
   while (*words != '\0')  /* while (*words)可以作为条件 */
       putchar(*words++);  /*指针类型典型应用*/
}

/*自定义打印函数2*/
void putx2(const char strings[])
{
    int i = 0;
    while (strings[i] != '\0')
        putchar(strings[i++]);  /*数组类型*/

}

11.4 字符串函数

11.4.1 strcat()函数

作用:拼接字符串,把第2个字符串的备份附加到第1个字符串末尾!

问题:会覆盖第1个字符串末尾的空字符\0吗?

  • 第1个字符改变
  • 第2个字符不改变

缺点:无法确定第1个数组是否有足够多的容量,与gets()函数相似,会导致严重后果缓冲区溢出!!!

11.4.2 strncat()函数
  • strncat(first, second, 10)
    • 将第2个参数的前10个字符拼接到第一个字符之后
    • 遇到第N个字符空字符时停止

代码

#include <stdio.h>
#include <string.h>

int main (void)
{
    char first[100] = "carter ";
    char * second = "悯Aristo 海角天涯-廖";
    strncat(first, second, 8);
    puts(first);
    puts(second);
    return 0;
}

结果

carter 悯Aristo
悯Aristo 海角天涯-廖

11.4.3 strcmp()函数

strcmp(A, B)

  • 返回值:A - B 两者ASCII码的差值
  • 比较的是字符串而不是字符

非零都为"真"

  • 相同:0
  • 不相同:非0

缺点:从头到尾比较两个字符串

问题:仅比较两个单词前缀是否相同时,该如何进行处理?

代码

#include <stdio.h>
#include <string.h>
#define ANSWER "hello"
const int SIZE = 6;

int main (void)
{
    char try[SIZE];

    printf("Enter a word: ");
    scanf("%s", try);  /*字符数组名就代表首元素地址*/

    printf("value01 = %d\n", try == ANSWER);  /*此时比较的是两个地址是否相同*/

    /*strcmp()函数,此时比较的是两个字符串的内容,同:0,不同:1*/
    printf("value02 = %d\n", strcmp(try, ANSWER));
    
    /*=====!错误的理解!=====*/
    /*比较两个地址指向的内容是否一致,同:1(true) 不同:0(false)*/
    printf("\n%#p\n%#p\nvalue03 = %d",*try, *ANSWER, *try == *ANSWER);
    return 0;
}

演示

value01 = 0 不同
value02 = 0 strcmp相同

!错误的理解!

0X0000000000000068
0X0000000000000068
value03 = 1 相同,指向同一地址元素

11.4.4 strncmp()函数

优点:可以比较两字符串不同的地方,也可以比较指定位置的字符

知识拓展#define和const的区别(参考链接)

代码

#include <stdio.h>
#include <string.h>
#define MAX 6  /*正确声明:表示一个常量 预编译:编译前的处理 常数*/
//const int MAX = 6;  /*错误:Int型变量,初始化为常数!variable-sized object may not be initialized可以用变长数组,但是变量不能初始化!!!*/


int main (void)
{
    const char *words[MAX] =
    {
        "aristoxxx01", "aristoxxx02",
        "filename", "teleScope",
        "aristoCritic", "glove"
    };

    int count = 0;
    for (int i = 0; i < MAX; i++)
        if (strncmp("aristo", words[i], 6) == 0)
        {
            printf("Found: %s\n", words[i]);
            count++;
        }
    printf("The list contained %d words beginning"
            " with aristo.\n", count);

    return 0;
}

演示

Found: aristoxxx01
Found: aristoxxx02
Found: aristoCritic
The list contained 3 words beginning with aristo.

11.4.5 strcpy()函数

性能

  • ptr2 = ptr1 地址拷贝
  • strcpy()函数 字符串拷贝
  • 后面的字符覆盖前面的字符(首字符位置可以变换)
  • 第一个参数不必指向数组的开始

缺点

  • 无法确定目标字符数组是否能够容纳,缓冲区溢出!!!
  • 不安全

代码

char * ps;
const char * orig = "beast";
char copy[SIZE] = "Be the best that you can be.";

ps  = strcpy(copy + 7, orig);  /*ps返回值:指向copy中第8个元素之后*/
puts(copy);
puts(orig);
puts(ps);

结果

Be the beast  ===将后面的全部用beast覆盖掉===
beast   ===需要拷贝的源字符===
beast  ===copy第8个元素后的字符===

语法错误

char target[10];
target = "carter";  /*语法错误,编译不通过*/
puts(target);
11.4.6 strncpy()函数

strncpy(A, B, n)

  • 将B拷贝到A中
  • A是目标字符串
  • B是源字符串
  • 最多拷贝n个字符
    • B字符数<n时:目标字符串A能够带上B的空字符
    • B字符数>n时:目标字符串A不一定带有空字符 ,所以必须A[MAX - 1] = ‘\0’,保证A成立为字符串
11.4.7 sprintf()函数

声明在stdio.h中,能够把数据写入字符串,将多个元素组合成一个字符串,相当于字符串的格式化

字符串组合函数

char formal[100];  /*目标字符串声明*/
char firstName[20] = "廖";
char lastName[20] = "述幸";
double price = 379.99;

sprintf(formal, "%s, %-4s, $%6.2f", lastName, firstName, price);  /*字符串组合函数*/
puts(formal);  /*输出组合后的字符串*/

结果

述幸, 廖 , $379.99

11.5 命令行参数

  • 从命令行中读取的都是字符串
  • atoi()函数:将字符串转变成int类型

代码

/*命令行参数*/
#include <stdio.h>

int main (int argc, char * argv[])
{
    printf("命令行中字符串数量:%d\n", argc);
    printf("命令行中参数值:\n");
    
    for (int i = 0; i < argc; i++)
        printf("\targv[%d] = %s\n", i, argv[i]);

    return 0;
}

=====注意=====
	一般需要对字符串个数进行判定。
	如:if(argc < 2) return -1;

结果

PS D:\FileDir\Java\VSCode\CLanguage\Chapter-11> .\command_line_argument 11 12 "hello" world "this is my gloves"
命令行中字符串数量:6
命令行中参数值:
        argv[0] = D:\FileDir\Java\VSCode\CLanguage\Chapter-11\command_line_argument.exe
        argv[1] = 11
        argv[2] = 12
        argv[3] = hello
        argv[4] = world
        argv[5] = this is my gloves

11.6 ctype.h 字符函数和字符串

代码

#include <stdio.h>
#include <ctype.h>
#include <string.h>

void ToUpper (char * str);

int main (void)
{
    char strings[] = "carter no\nhello\nworld\n";
    char * find;
    find = strchr(strings, '\n');  //找到第一处换行符
    if (find)  // 如果地址不是NULL
        *find = '\0';  //字符串结束符号
    ToUpper(strings);
    puts(strings);
    return 0;
}

/**
 * @brief 将字符串里所有字符大写
 * 
 * @param str 字符串指针
 */
void ToUpper (char * str)
{
    while (*str)
    {
        *str = toupper(*str);  // 仅能作用于单个字符
        str++;
    }
}

演示

CARTER NO

第12章 存储类别、链接和内存管理

12.1 存储类别

5种存储类别

存储类别存储期作用域链接声明方式
自动自动块内
寄存器自动块内,使用关键字register
静态外部链接静态文件外部所有函数外
静态内部链接静态文件内部所有函数外,使用关键字static
静态无链接静态块内,使用关键字static
12.1.1 作用域
  1. 块作用域:花括号括起来的代码区域
  2. 函数作用域:仅用于goto语句的标签
  3. 函数原型作用域:形参定义处 ===> 原型声明结束
  4. 文件作用域:变量定义在函数外

文件作用域变量:也称全局变量

12.1.2 链接
  1. 外部链接:文件作用域变量(全局变量)
  2. 内部链接:static文件作用域变量(静态全局变量)
  3. 无链接:块作用域、函数作用域、函数原型作用域的变量

翻译单元:一个源代码文件和它所包含的头文件

12.1.3 存储期
  1. 静态存储周期:程序执行期间一直存在,所有文件作用域变量都具有静态存储周期和 static 关键字修饰的
  2. 线程存储周期:用于并发程序设计,关键字_Thread_local
  3. 自动存储周期:块作用域
  4. 动态分配存储周期:变长数组
12.1.4 块作用域的静态变量
  • 仅初始化一次
  • 地址不改变
  • 内容改变

代码

#include <stdio.h>

int main (void)
{
    for(int i = 1; i <= 10; i++)
    {
        static int total = 0;  /*静态、无链接、块作用域的变量,静态变量未被初始化就会自动初始化为0,仅初始化一次*/
        total += i;
        printf("第%d次, total = %d\n", i, total);
    }
    return 0;
}

结果

第1次, total = 1
第2次, total = 3
第3次, total = 6
第4次, total = 10
第5次, total = 15
第6次, total = 21
第7次, total = 28
第8次, total = 36
第9次, total = 45
第10次, total = 55

12.1.4 外部链接的静态变量

关键字extern

  • 引用现有的外部定义
  • 不分配内存

tools.h

/*静态文件作用域*/
static int externalNumber = 9;

controller.c

#include <stdio.h>
#include "tools.h"  /*externalNumber所在头文件,双引号表明被包含的文件位于当前目录中*/

/*外部链接的静态变量,必须如此声明*/
extern int externalNumber;

int main (void){
    printf("externalNumber = %d", ++externalNumber);
    return 0;
}

结果

externalNumber = 10

12.2 随机数函数和静态变量

ANSI C库提供了rand()函数生成随机数—伪随机数生成器,同样的代码运行两次,得到同样的随机数

解决方案:使用time()函数,重置种子

12.3 分配内存 malloc() 和 free()函数

malloc()函数

  • 指针接收
  • 和free()搭配使用
  • 原型在stdlib.h头文件中
  • 如果分配失败,返回NULL指针,调用exit()函数退出程序
    • EXIT_FAILURE
    • EXIT_SUCCESS(相当于0),正常结束程序

free()函数

  • 自动变量使用的内存数量在程序执行期间自动增加或减少
  • 动态分配的内存数量只会增加,除非使用free()进行释放
  • 不使用free()会导致内存泄漏(memory leak),内存耗尽!!!

3种创建数组的方法

  • 声明数组—常量表达式
  • 声明变长数组(C99新特性)—变量表达表示数组的维数
  • 声明一个指针—调用malloc()函数,将其返回值赋给指针

12.4 存储类别和动态内存分配

  • 静态存储类别
    • 内存数量:编译时确定
    • 创建:程序执行时
    • 销毁:程序结束时
  • 自动存储类别
    • 内存数量:随着程序调用进行相对应的增加和减少
    • 创建:进入所在块时
    • 销毁:离开所在块时
    • 这部分内存通常作为栈来处理(后进先出
  • 动态分配的内存
    • 创建:调用malloc()函数时
    • 销毁:调用free()函数时
    • 一般来说,动态内存栈内存慢!

12.5 const 限定符

  • const float * pf 值不变
  • float * const pf 指针指向不变
  • float const * pf 值不变
  • const float * const pf 值不变+指针指向也不变

总结:左值右指针不变

第13章 文件输入/输出

13.1 文件模式

  • 文本内容

  • 二进制内容

  • 文本文件格式

  • 二进制文件格式

C提供两种访问文件的途径

  • 文本模式:程序所见的内容和文件内容不同,会把本地环境表示的行末尾或文件结尾映射为C模式
  • 二进制模式:程序可以访问文件每个字节,不会发生映射

13.2 I/O 级别

  • 底层I/O(low-level I/O):使用操作系统提供的基本I/O服务。(可能只能适用指定系统)
  • 标准高级I/O(standard hight-level I/O)使用C库的标准包和 stdio.h 头文件定义。(可移植性强

13.3 I/O 函数介绍

13.3.1 return() 和 exit()函数
  • exit() 仍然会终止程序
  • return 只会把控制权交给上一级递归,直至最初的一级
13.3.2 fopen()函数

fopen(文件名, 打开模式)

  • 以下都是文件模式打开文件
模式字符串含义
“r”读模式
“w”写模式;清除文本内容,不存在就创建
“a”写模式;尾部新添,不存在就创建
“r+”更新模式;读写文件
“w+”更新模式;读写文件,清除文本内容,不存在就创建
“a+”更新模式;读写文件,文件末尾添加,不存在就创建
13.3.3 fprintf() 和 fscanf()函数

代码

fprintf(stdout, "Do one's best level");  /*标准输出*/
printf("Do one's best level");  /*作用同上*/

int i;
fscanf(stdin, "%d", &i);  /*stdin 标准输入*/
scanf("%d", &i);  /*同上*/


rewind(fp);  /*回到文件开始处*/
fscanf(fp, "%s", words) == 1;  /*扫描文本内容,扫描单词(不含空格)*/
13.3.4 fgets() 和 fputs()函数
  • fgets(字符数组, 字符串大小, 文件指针):保留换行符
  • fputs(字符数组,文件指针):不添加换行符
13.3.5 fseek() 和 ftell()函数

fseek()函数

  • 在fopen()打开的文件中直接移动到任意字节处

例:fseek(file, 0L, SEEK_SET) —> 定位至文件开始处

模式偏移量的起始点
SEEK_SET文件开始处
SEEK_CUR当前位置
SEEK_END文件末尾

ftell()函数

  • 返回 long 类型值(限制了文件字符大小),表示文件中的当前位置
函数调用效果
fseek(file, 0L, SEEK_SET)定位至文件开始处
fseek(file, 0L, SEEK_CUR)保持当前位置不动
fseek(file, 0L, SEEK_END)定位至文件结尾
fseek(file, ftell-pos, SEEK_SET)到距离文件开始处 ftell-pos 的位置,ftell-pos 是 ftell() 的返回值

潜藏的问题

long类型的返回值类型,导致数据大小受限,并不适用于大数据时代。

13.3.6 fgetpos() 和 fsetpos()函数

新类型 fpos_t (代表 file position type , 文件定位类型)

fpos_t不是基本数据类型

函数原型

  • int fgetpos (FILE * restrict, fpos_t * restrict pos);
  • int fsetpos(FILE *stream, const fpost_t *pos)

13.4 其它标准 I/O 函数

13.4.1 ungetc()函数

作用:把指定的字符放回输入流中,ANSI C标准保证每次只放回一个字符。

参数:int ungetc(int c ,FILE *fp)

13.4.2 fflush()函数

作用:刷新缓冲区,用于更新流(任何读写模式)

参数:int fflush(FILE *fp)

13.4.3 fread() 和 fwrite()函数

:如何在文件中保存数值数据

:使用fprintf()将数值转化成字符串

double num = 1. / 3.;
fprintf(fp, "%f", num);

:改变转换说明,就会改变存储该值所需要的空间数量,也会导致存储不同的值!并且无法恢复为更高的精度!

:如何保证数值在存储前后一致?

:最精确的做法是使用与计算机相同的位组合来存储。

:因此,double 类型的值应该存储在一个 double大小的单元中。

​ 如果以程序所用的表示法把数据存储在文件中,则称为以二进制形式存储数据。不存在从数值形式—>字符串的转化过程

对于标准的 I/O,fread()fwrite 函数用于以二进制形式处理数据。

fwrite()函数原型

作用:把二进制数写入文件

参数:size_t fwrite (const void * restrict ptr, size_t size, size_t nmemb, FILE * restrict fp);

案例:fwrite(buffer, 256, 2, fp)

​ 把2块256字节的数据从 buff 写入文件fp,返回写入的的字符大小。

fread()函数原型

作用:读取文件内容

参数:size_t fread(void * restrict ptr, size_t size, size_t nmeb, FILE * fp);

案例:fread(earning, sizeof(double), 10 fp);

​ 把10个double 大小的值拷贝进earnings数组中。

第14章 结构和其他数据形式

14.1 结构声明/定义

实例

struct book {
    char title [MAXTITLE];
    float value;
};

此时并未让编译器分配空间

14.1.1 访问结构成员

结构成员运算符----点(.)访问结构种的成员

&book.value:点的优先级大于&

实例

/*结构的初始化方式*/
#include <stdio.H>
#define MAXTITLE 41

/*book 结构体声明*/
struct book {
    char title[MAXTITLE];
    float value;

};

int main (void)
{
    struct book gift = {.value = 11.99, .title = "Swing", 11.98, 11.90}; /*11.90哪儿去了?*/
    printf("title = %s  value= %f", gift.title, gift.value);
    return 0;
}

警告⚠

structInitializer.c:14:66: warning: excess elements in struct initializer //元素溢出
    struct book gift = {.value = 11.99, .title = "Swing", 11.98, 11.90};
                                                                  ^~~~~
structInitializer.c:14:66: note: (near initialization for 'gift')
title = Swing  value= 11.980000
14.1.2 标识结构数组的成员
/*结构数组*/
#include <stdio.h>
#define MAXTITLE 41
const int MAX = 5;

struct book {
    char title [MAXTITLE];
    float value;
};

int main (void)
{
    struct book libry[MAX];
    libry;                  // 一个book结构的数组
    libry[2];               // 第三个数组元素,该元素是book结构
    libry[2].title;         // 一个char型字符数组,libry[2]内的成员title
    libry[2].title[4];      // 一个char型字符
    return 0;
}

14.2 指针结构的指针

14.2.1 指针优点
  1. 指针结构的指针比结构本身更容易操作
  2. 早期C语言中,结构不能作为参数传递给函数,但可以传递指针结构的指针
  3. 通常传递指针效率更高
  4. 一些用于表示数据的结构种包含指向其它结构的指针
14.2.2 指针访问结构成员
  • 方式1:him -> value;
  • 方式2:(*him).value;
struct book *it;
it -> value;  // 与&libry[0].value == (*it).value
it++;  // 指针的特点

14.3 结构特性

注意:允许把一个结构赋值给另一个结构,但数组无法赋给另一个

Q:数组相互之间如何赋值?

A:使用结构体包装数组!!!

14.3.1 结构体-形参

声明

struct book getInfo(struct book);  // 返回:book型结构;形参:book型结构
14.3.2 结构和结构指针的选择

指针

  • 优点
    • 效率高
    • 普遍性
  • 缺点
    • 无法保护数据—const限定符(ANSI C)

结构本身

  • 优点
    • 处理的是原始数据的副本,保护了原始数据
    • 代码风格更加清楚
  • 缺点
    • 老版本只能传指针
    • 浪费时间&存储空间
14.3.3 结构中的字符数组和字符指针

字符串存储声明的两种形式:

①字符数组

②字符指针

#define LEN 20
struct names {  // ①字符数组(简单)
    char first[LEN];
    char last[LEN];
};

struct pnames {  // ②字符指针,滥用会导致严重后果
    char * first;
    char * last;
};

int main (void)
{
    struct pnames ptr;
    char temp[LEN];
    ptr -> first = (char *)malloc (strlen(temp) + 1);  // malloc函数进行内存分配
    
    free(ptr -> first);  // 释放内存资源
    return 0;
}

14.4 伸缩数组(C99)

14.4.1 声明带伸缩数组结构的规则
  • 伸缩数组成员必须是结构的最后一个成员
  • 结构中必须至少有一个成员
  • 伸缩数组的声明和普通数组类似,只是方括号中式
struct flex
{
    int count;
    double average;
    double scores[];  // 伸缩数组成员
};

struct plex *pf; // ①声明一个指针结构的指针

pf = malloc(sizeof(struct flex) + 5 * sizeof(double));  // ②请为一个结构和一个数组分配存储空间
pf->count = 5;
pf->scores[2] = 18.2;  // 可以使用
14.4.2 伸缩数组成员的规定

带伸缩数组的结构

  1. 不能用结构进行赋值拷贝(只能拷贝除伸缩数组外的成员)
  2. 带伸缩数组的结构不能成为别人的成员或数组成员
  3. 做参数:不要传递结构,要传递地址

14.5 联合Union

14.5.1 联合的声明
union hold {
    int digit;
    double bigfl;
    char letter;
};  // 和结构的声明一致
14.5.2 联合和结构区别

如果14.5.1声明的是个结构,那它可存储一个int型、一个double型和一个char型

联合:只能存储一个,要么存一个int,要么一个double或者char

​ 14.5.1的联合中,声明一个hold联合,仅会分配一个double数据(8字节)大小的内存空间

后面会覆盖前面的赋值语句

联合命名

struct data {
    char make[15];
    int status;
    union {
        struct owner ow;
        int x;
    };
}

14.6 枚举类型

枚举类型的目的:提高程序的可读性可维护性

枚举类型是整数类型

/*枚举类型测试程序*/
#include <stdio.h>
enum spectrum {red, orange, yellow, green, blue, violet};  /*枚举类型是整数类型*/

int main (void)
{
    enum spectrum color;
    color = blue;
    if (color == blue)
        puts("It is blue...");
    for (color = red; color <= violet; color++)  /*C语言中能够使用++,但是C++不允许*/
        printf("%d\n", color);  /*枚举类型是整数类型 %d接收*/
        
    return 0;
}

演示

It is blue...
0
1
2
3
4
5

枚举类型可以指定整数值

enum levels {cat, dog = 10, cow, rat, horse}; /*注意其中cat == 0, dog, cow, rat == 10, 11, 12*/

14.7 typedef 简介

14.7.1 typedef 和 #define 的区别

typedef :为某一类型(不能是值)自定义名称,通常大写

typedef

char * STRING;  // 没有typedef:编译器把STRING识别为一个指向char的指针变量

typedef char * STRING; // 有typedef:编译器把STRING解释成一个类型的标识符,该类型指向char的指针

STRING name, sign;
// 注意:相当于:
char * name, * sign;

#define

#define STRING char *
STRING name, sign;
// 注意:相当于
char * name, sign;

#define bool _Bool
#define true 1
#define false 0
14.7.2 typedef 修饰结构
/*tyoedef修饰结构*/
#include <stdio.h>

typedef struct {
    int x;
    int y;
} rect;

int main (void)
{
    rect r1 = {3, 4};
    printf("r1.x = %d, r1.y = %d\n", r1.x, r1.y);

    rect r2;
    printf("r2.x = %d, r2.y = %d\n", r2.x, r2.y);

    r2 = r1;
    printf("r2.x = %d, r2.y = %d\n", r2.x, r2.y);
    return 0;
}

演示

r1.x = 3, r1.y = 4
r2.x = 1643472, r2.y = 0
r2.x = 3, r2.y = 4

注意

rect r1 = {3, 4};
rect r2;
// 可以被翻译为:
struct {int x; int y;} r1 = {3, 4};
struct {int x; int y;} r2;

14.8 其它复杂的声明

​ 表14.1 声明时可使用的符号

符号含义
*表示一个指针
()表示一个函数
[]表示一个数组

下面是一些复杂的声明:

/* 
[]是结合律优先级高于*符号 
先指针--->是指针
先数组--->是数组
*/

int board[8][8];		// 声明一个内含int数组的数组

int ** ptr;				// 声明一个指向指针的指针,被指向的指针指向int

int * reisks[8];		//  声明一个内含8个元素的数组,每个元素都是指向int的指针---指针数组

int (* resks)[8];		// 声明一个指向数组的指针, 该数组内含8个int类型数据---指向数组的指针

int * off[3][4];		// 声明一个内含3x4个元素的数组,每个数组都是指向int的指针

int (* uff)[3][4];		// 声明一个指向数组的指针,该数组内含3x4个int类型数据

int (* uof[3])[4];		// 声明一个内含3个指针元素的数组,每个指针都指向一个内含4个int类型元素的数组

14.9 指向函数的指针

void (*pf) (char *);  // pf 是一个指向函数的指针,参数列表是(char *),返回值是void
void *pf (char *);  // pf 是一个返回字符指针的函数

void ToUpper(char *);
pf = ToUpper;  // 允许

Page415

警告:本文档仅用于个人学习!!!

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值