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 函数定义-多个
- 声明函数(函数原型)
- 定义函数
二者的参数和返回值保持一致
#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 存储方式-浮点数
符号位 | 小数部分 | 指数部分 |
---|---|---|
+ | .3141592 | 1 |
结果:3.141592
3.2 表示方式-进制
八进制(octal)
:用0开头
十六进制(hexadecimal)
:以 0X 或 0x 开头
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:编译器会把 int 或 long 替换成与当前系统匹配的类型
- 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
- sizeof(values)===>返回 5x4 =
不适用于二维数组遍历
代码
#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"是一个字符串 | |
---|---|---|
存储形式 | 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 <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]地址
**zipoo
:zipoo[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个字符或空字符时停止
- 将第2个参数的
代码
#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 作用域
- 块作用域:
花括号
括起来的代码区域 - 函数作用域:仅用于
goto
语句的标签 - 函数原型作用域:形参定义处 ===> 原型声明结束
- 文件作用域:变量定义在
函数外
文件作用域变量:也称全局变量
12.1.2 链接
- 外部链接:文件作用域变量(全局变量)
- 内部链接:
static
文件作用域变量(静态全局变量) - 无链接:块作用域、函数作用域、函数原型作用域的变量
翻译单元:一个源代码文件和它所包含的头文件
12.1.3 存储期
- 静态存储周期:程序执行期间一直存在,所有
文件作用域
变量都具有静态存储周期和static
关键字修饰的 - 线程存储周期:用于并发程序设计,关键字_Thread_local
- 自动存储周期:块作用域
- 动态分配存储周期:变长数组
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 指针优点
- 指针结构的指针比结构本身更容易操作
- 早期C语言中,结构不能作为参数传递给函数,但可以传递指针结构的指针
- 通常传递指针效率更高
- 一些用于表示数据的结构种包含指向其它结构的指针
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 伸缩数组成员的规定
带伸缩数组的结构
- 不能用结构进行
赋值
和拷贝
(只能拷贝除伸缩数组外的成员) - 带伸缩数组的结构不能成为别人的成员或数组成员
- 做参数:不要传递结构,要传递地址
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
警告:本文档仅用于个人学习!!!