目录
第8章 字符输入/输出和输入验证
8.1 单字符I/O:getchar()和putchar()
getchar()和 putchar()每次只处理一个字符
while ((ch = getchar()) != '#')
使用 while 循环,当读到#字符时停止。
8.2 缓冲区
为什么要有缓冲区?
首先,把若干字符作为一个块进行传输比逐个发送 这些字符节约时间。
其次,如果用户打错字符,可以直接通过键盘修正错 误。当最后按下Enter键时,传输的是正确的输入。
第9章 函数
C的设计思想是,把函数用作构件块
9.1 函数概述
函数(function)是完成特定任务的独立程序代码单元,语法规则定义了函数的结构和使用方式。
1、创建并使用简单的函数
void starbar(void); /* 函数原型 */
int main(){
...
starbar(); /* 调用函数 */
}
void starbar(void) /* 定义函数 */{
...
}
函数原型(function prototype)告诉编 译器函数starbar()的类型;
一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型
第1个void是函数类型,void类型表明函数没有返回值;第2个void(在圆括号中)表明该函数不带参数。
函数调用(function call)表明在此处执行函数;
函数定义(function definition)明确地指定了函数要做什么
2、创建带有形参的函数
void show_n_char(char ch, int num);
形式参数也是局部变量,属该函数私有。
3、调用带实际参数的函数
实际参数和形式参数
- 实际参数是出现在函数调用圆括号中的表达式。
- 形式参数是函数定义的函数头中声明的变量。
调用函数时,创建了声明为形式参数的变量并初始化为实际参数的求值结果。
4、使用return从函数中返回值
关键字return后面的表达式的值就是函数的返回值
返回值不一定是变量的值,也可以是任意表达式的值。
return (n < m) ? n : m;
使用 return 语句的另一个作用是,终止函数并把控制返回给主调函数的 下一条语句。
5、函数类型
使用 return 语句的另一个作用是,终止函数并把控制返回给主调函数的 下一条语句。
函数类型指的是返回值的类型,不是函数参数的类型。
double klink(int a, int b);
返回值是double类型
9.2 ANSI C函数原型
9.3 递归
C允许函数调用它自己,这种调用过程称为递归
1、递归的基本原理
- 每级函数调用都有自己的变量
- 每次函数调用都会返回一次
- 递归函数中位于递归调用之前的语句,均按被调函数的顺序执行
- 递归函数中位于递归调用之后的语句,均按被调函数相反的顺序执行
- 递归调用非常类似于一个循环语句。 实际上,递归有时可用循环来代替,循环有时也能用递归来代替
2、尾递归
最简单的递归形式是把递归调用置于函数的末尾,即正好在 return 语句之前,这种形式的递归被称为尾递归。
3、递归的优缺点
- 优点是递归为某些编程问题提供了最简单的解决方案
- 缺点是一些递归算法会快速消耗计算机的内存资源
9.5 查找地址:&运算符
一元&运算符给出变量的存储地址
scanf()函数中就使用地址作为参数
9.7 指针简介
指针(pointer)是一个值为内存地址的变量
ptr = &pooh; // 把pooh的地址赋给ptr
要创建指针变量,先要声明指针变量的类型。
1、间接运算符:*
ptr = &bah;
val = *ptr; // 找出ptr指向的值
val = bah;
2、声明指针
int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf, * pg; // pf、pg都是指向float类型变量的指针
*和指针名之间的空格可有可无。
pc指向的值(pc)是char类型。pc本身是什么类型?我们描述它的类型 是*“指向char类型的指针”**。
3、使用指针在函数间通信
void interchange(int * u, int * v);
int main(){
...
interchange(&x, &y); // 把地址发送给函数
}
普通变量把值作为基本量,把地址作为通过&运算符获得的 派生量;
而指针变量把地址作为基本量,把值作为通过*****运算符获得的派生量。
第10章 数组和指针
10.1 数组
数组由数据类型相同的一系列元素组成
float candy[365]; /* 内含365个float类型元素的数组 */
char code[12]; /*内含12个char类型元素的数组*/
int states[50]; /*内含50个int类型元素的数组 */
1、初始化数组
int powers[8] = {1,2,4,6,8,16,32,64};
/* 从ANSI C开始支持这种初始化 */
用以逗号分隔的值列表(用花括号括起来)来初始化数组, 各值之间用逗号分隔,在逗号和值之间可以使用空格。
2、给数组元素赋值
int counter, evens[SIZE];
for (counter = 0; counter < SIZE; counter++)
evens[counter] = 2 * counter;
...
}
这段代码中,使用循环给数组的元素依次赋值。
C 不允许把数组作为 一个单元赋给另一个数组,除初始化以外也不允许使用花括号列表的形式赋值。
int oxen[SIZE] = {5,3,2,8}; /* 初始化没问题 */
int yaks[SIZE];
yaks = oxen; /* 不允许 */
yaks[SIZE] = oxen[SIZE]; /* 数组下标越界 */
3、数组边界
在使用数组时,要防止数组下标超出边界。也就是说,必须确保下标是有效的值。
数组元素的编号从0开始。最好是在声明数组时使用符号常量来表示数组的大小。
4、指定数组的大小
声明数组时只能在方括号中使用整型常量表达式。
整型常量表达式,是由整型常量构成的表达式。
10.2 多维数组
float rain[5][12];
// 内含5个数组元素的数组,每个数组元素内含12个
1、初始化二维数组
初始化二维数组是建立在初始化一维数组的基础上。
float rain[YEARS][MONTHS] =
{ {4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,7.3},
...
};
这个初始化使用了数值列表,每个数值列表都用花括号括起来。
初始化时也可省略内部的花括号,只保留最外面的一对花括号。只要保证初始化的数值个数正确,初始化的效果与上面相同。
2、其他多维数组
int box[10][20][30];
通常,处理三维数组要使用3重嵌套循环,处理四维数组要使用4重嵌套循环。
10.3 指针和数组
如果flizny是一个数组,下面的语句成立:
flizny == &flizny[0];
// 数组名是该数组首元素的地址
可以把它们赋值给 指针变量,然后可以修改指针变量的值:
int dates[y],*pti;
pti = dates;
//pti = & dates[0];
在指针前面使用*运算符可以得到该指针所指向对象的值。
指针加1,指针的值递增它所指向类型的大小:
dates + 2 == &date[2] // 相同的地址
*(dates + 2) == dates[2] // 相同的值
10.4 函数、数组和指针
以下两种函数原型是等价的:
int sum(int *ar, int n);
int sum(int ar[], int n);
因为数组名是该数组首元素的地址,作为实际参数的数组名要求形式参 数是一个与之匹配的指针
1、使用指针形参
是传递两个指针,第1个指针指明数组的开始处(与 前面用法相同),第2个指针指明数组的结束处:
int main(){
int marbles[SIZE] = { 20, 10, 5, 39, 4, 16, 19, 26,31, 20 };
answer = sump(marbles, marbles + SIZE);
...
}
int sump(int * start, int * end)
{
int total = 0;
while (start < end) //用来结束循环
{
total += *start; // 把数组元素的值加起来
start++; // 让指针指向下一个元素
//total += *start++;
//先把指针指向位置上的值加到total上,然后再递增指针
}
}
指针start开始指向marbles数组的首元素,然后,表达式start++递增指针变量start,使其指向数组的下一个元素。
2、指针表示法和数组表示法
ar[i]和*(ar+1)这两个表达式都是等价的
但是,只有当ar是指针变量时,才能使用**ar++**这样的表达式
10.5 指针操作
int urn[5] = { 100, 200, 300, 400, 500 };
**赋值:**可以把地址赋给指针
ptr1 = urn; // 把一个地址赋给指针
ptr2 = &urn[1]; // 把一个地址赋给指针
**解引用:**运算符给出指针指向地址上储存的值
printf("ptr1 = %p, *ptr1 =%d, &ptr1 = %p\n", ptr1, *ptr1, &ptr1);
ptr1 = 000000000061FE00, *ptr1 =100, &ptr1 = 000000000061FDF8
**取址:**和所有变量一样,指针变量也有自己的地址和值
**指针与整数相加:**可以使用+运算符把指针与整数相加,或整数与指针相加。
ptr1 + 4 = 000000000061FE10, *(ptr1 + 4) = 500
**递增指针:**递增指向数组元素的指针可以让该指针移动至数组的下一个元素。
ptr1++;
ptr1 = 000000000061FE04, *ptr1 =200, &ptr1 = 000000000061FDF8
指针减去一个整数:可以使用-运算符从一个指针中减去一个整数。
**递减指针:**同递增指针
ptr2 = 000000000061FE00, *ptr2 = 100, &ptr2 = 000000000061FDF0
**指针求差:**可以计算两个指针的差值。
ptr2 = 000000000061FE04, ptr1 = 000000000061FE00, ptr2 - ptr1 = 1
通常,求差的两个指针分别指向 同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。
10.6 保护数组中的数据
1、对形式参数使用const
如果函数的意图不是修改数组中的数据内容,那么在函数原型和函数定义中声明形式参数时应使用关键字const。
int sum(const int ar[], int n); /* 函数原型 */
该函数不能修改ar指向的数组中的内容
2、const的其他内容
虽然用#define指令可以创建类似功能的符号常量,但是const的用法更加灵活。
可以创建const数组、const指针和指向const的指针。
指向const的指针不能用于改变值:
double rates[5] = {88.99, 100.12, 59.45, 183.11, 340.5};
const double * pd = rates;
// pd指向数组的首元素