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  /*头文件*/
/**
 * @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 
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 存储方式-浮点数

符号位 小数部分 指数部分
+ .3141592 1

结果:3.141592

3.2 表示方式-进制

八进制(octal):用0开头

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

3.3 显示方式-进制

  • 将数以xx进制进行显示

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

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

代码

#include 

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   /*新增了两个头文件*/
#include   /*浮点数的上下溢==>FLT_MAX、FLT_MIN*/
#include  /*整数上溢==>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 
#include   /*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"是一个字符串
存储形式 x X\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 

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

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

结果

| 廖|
|廖 |

代码

#include 
#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 

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 

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 

/*输入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 
#include 

/*
获取字符串大小
①使用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 

/*除法运算*/
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 
#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 

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 

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 
#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 

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 

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

演示

q
ch = [q]

联合使用

#include 
#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 
#include   /*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 

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 

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 
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 

/*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 

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 
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 
#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 
#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 
#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 
#include 

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 
#include 
#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 
#include 
#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字符数
    • 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 

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 
#include 
#include 

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 

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 
#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 
#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 
#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 
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 

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

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

你可能感兴趣的:(C/C++语言,c语言)