C语言笔记(4)

C语言笔记(4)

关键词:

getchar()和 scanf()属于行缓冲输入,键盘输入Enter键会传送一个换行符需注意,文本输入无碍;
函数避免代码重复,让程序更加模块化, 提高程序代码的可读性, 更方便后期维护;
声明函数类型指的是返回值的类型, 不是函数参数的类型;
递归注意合适结束,相较于循环消耗内存严重;
斐波那契数列指数增长的变量数量很快就消耗掉计算机的大量内存, 很可能导致程序崩溃;
ptr = &pooh; // 把pooh的地址赋给ptr;
val = *ptr; // 找出ptr指向的值赋给val;
int * pi;声明的意思是pi是一个指针, *pi是int类型,指针声明要强调类型;
double sales[row][column];变长数组(VLA)自动存储类型,row和column确定好数组维度不可变;
声明一维数组形式:type name [ size ];
若pd指向一个8字节double类型数组,那么pd加1意味着其值加8,以便它指向该数组的下一个元素;

1.许多程序使用 getchar()逐字符读取输入。 通常, 系统使用行缓冲输入,即当用户按下 Enter 键后输入才被传送给程序。 按下Enter键也传送了一个换行符, 编程时要注意处理这个换行符。 ANSI C把缓冲输入作为标准。
通过标准I/O包中的一系列函数, 以统一的方式处理不同系统中的不同文件形式, 是C语言的特性之一。 getchar()和 scanf()函数也属于这一系列。当检测到文件结尾时, 这两个函数都返回 EOF(被定义在stdio.h头文件中) 。 在不同系统中模拟文件结尾条件的方式稍有不同。 在UNIX系统中,在一行开始处按下Ctrl+D可以模拟文件结尾条件; 而在DOS系统中则使用Ctrl+Z。
许多操作系统(包括UNIX和DOS) 都有重定向的特性, 因此可以用文件代替键盘和屏幕进行输入和输出。 读到EOF即停止读取的程序可用于键盘输入和模拟文件结尾信号, 或者用于重定向文件。
混合使用 getchar()和 scanf()时, 如果在调用 getchar()之前, scanf()在输入行留下一个换行符, 会导致一些问题。 不过, 意识到这个问题就可以在程序中妥善处理。
编写程序时, 要认真设计用户界面。 事先预料一些用户可能会犯的错误, 然后设计程序妥善处理这些错误情况。

2.为什么要使用函数? 首先, 使用函数可以省去编写重复代码的苦差。 如果程序要多次完成某项任务, 那么只需编写一个合适的函数, 就可以在需要时使用这个函数, 或者在不同的程序中使用该函数, 就像许多程序中使用putchar()一样。 其次, 即使程序只完成某项任务一次, 也值得使用函数。 因为函数让程序更加模块化, 从而提高了程序代码的可读性, 更方便后期修改、 完善。

3.声明函数时必须声明函数的类型。 带返回值的函数类型应该与其返回值类型相同, 而没有返回值的函数应声明为void类型。 如果没有声明函数的类型, 旧版本的C编译器会假定函数的类型是int。 这一惯例源于C的早期, 那时的函数绝大多数都是int类型。 然而, C99标准不再支持int类型函数的这种假定设置。
类型声明是函数定义的一部分。 要记住, 函数类型指的是返回值的类型, 不是函数参数的类型。 例如, 下面的函数头定义了一个带两个int类型参数的函数, 但是其返回值是double类型。
double klink(int a, int b)
要正确地使用函数, 程序在第 1 次使用函数之前必须知道函数的类型。方法之一是, 把完整的函数定义放在第1次调用函数的前面。 然而, 这种方法增加了程序的阅读难度。 而且, 要使用的函数可能在C库或其他文件中。因此, 通常的做法是提前声明函数, 把函数的信息告知编译器。
函数形式:
典型的ANSI C函数的定义形式为:
返回类型 名称(形参声明列表)
函数体
形参声明列表是用逗号分隔的一系列变量声明。 除形参变量外, 函数的其他变量均在函数体的花括号之内声明。

4.C允许函数调用它自己, 这种调用过程称为递归(recursion) 。 递归有时难以捉摸, 有时却很方便实用。 结束递归是使用递归的难点, 因为如果递归代码中没有终止递归的条件测试部分, 一个调用自己的函数会无限递归。
可以使用循环的地方通常都可以使用递归。 有时用循环解决问题比较好, 但有时用递归更好。 递归方案更简洁, 但效率却没有循环高。
递归既有优点也有缺点。 优点是递归为某些编程问题提供了最简单的解决方案。 缺点是一些递归算法会快速消耗计算机的内存资源。 另外, 递归不方便阅读和维护。

5.斐波那契数列的定义如下: 第1 个和第2 个数字都是1, 而后续的每个数字都是其前两个数字之和。 例如, 该数列的前几个数是: 1、 1、 2、 3、 5、8、 13。 斐波那契数列在数学界深受喜爱, 甚至有专门研究它的刊物。 不过, 这不在本书的讨论范围之内。 下面, 我们要创建一个函数, 接受正整数n, 返回相应的斐波那契数值。
首先, 来看递归。 递归提供一个简单的定义。 如果把函数命名为Fibonacci(), 那么如果n是1或2, Fibonacci(n)应返回1; 对于其他数值, 则应返回Fibonacci(n-1)+Fibonacci(n-2):
unsigned long Fibonacci(unsigned n)
{ if
(n > 2)
return Fibonacci(n-1) + Fibonacci(n-2);
else
return 1;
}
这个递归函数只是重述了数学定义的递归。 该函数使用了双递归(double recursion) , 即函数每一级递归都要调用本身两次。 这暴露了一个问题。
为了说明这个问题, 假设调用 Fibonacci(40)。 这是第1 级递归调用, 将创建一个变量 n。 然后在该函数中要调用Fibonacci()两次, 在第2级递归中要分别创建两个变量n。 这两次调用中的每次调用又会进行两次调用, 因而在第3级递归中要创建4个名为n的变量。 此时总共创建了7个变量。 由于每级递归创建的变量都是上一级递归的两倍, 所以变量的数量呈指数增长! 在第 5章中介绍过一个计算小麦粒数的例子, 按指数增长很快就会产生非常大的值。 在本例中, 指数增长的变量数量很快就消耗掉计算机的大量内存, 很可能导致程序崩溃。

6.指针(pointer) 是 C 语言最重要的(有时也是最复杂的) 概念之一, 用于储存变量的地址。 前面使用的scanf()函数中就使用地址作为参数。 概括地说, 如果主调函数不使用return返回的值, 则必须通过地址才能修改主调函数中的值。 接下来, 我们将介绍带地址参数的函数。 首先介绍一元&运算符的用法。
一元&运算符给出变量的存储地址。 如果pooh是变量名, 那么&pooh是变量的地址。 可以把地址看作是变量在内存中的位置。 假设有下面的语句:
pooh = 24;
假设pooh的存储地址是0B76(PC地址通常用十六进制形式表示) 。 那么, 下面的语句:
printf("%d %p\n", pooh, &pooh);
将输出如下内容(%p是输出地址的转换说明) :
24 0B76

7.指针? 什么是指针? 从根本上看, 指针(pointer) 是一个值为内存地址的变量(或数据对象) 。 正如char类型变量的值是字符, int类型变量的值是整数, 指针变量的值是地址。 在C语言中, 指针有许多用法。 本章将介绍如何把指针作为函数参数使用, 以及为何要这样用。
假设一个指针变量名是ptr, 可以编写如下语句:
ptr = &pooh; // 把pooh的地址赋给ptr
对于这条语句, 我们说ptr“指向”pooh。 ptr和&pooh的区别是ptr是变量,而&pooh是常量。 或者, ptr是可修改的左值, 而&pooh是右值。 还可以把ptr指向别处:
ptr = &bah; // 把ptr指向bah, 而不是pooh
现在ptr的值是bah的地址。
要创建指针变量, 先要声明指针变量的类型。 假设想把ptr声明为储存int类型变量地址的指针, 就要使用下面介绍的新运算符。
假设已知ptr指向bah, 如下所示:
ptr = &bah;
然后使用间接运算符*(indirection operator) 找出储存在bah中的值, 该运算符有时也称为解引用运算符(dereferencing operator) 。 不要把间接运算符和二元乘法运算符(*) 混淆, 虽然它们使用的符号相同, 但语法功能不同。
val = *ptr; // 找出ptr指向的值
语句ptr = &bah;和val = *ptr;放在一起相当于下面的语句:
val = bah;
由此可见, 使用地址和间接运算符可以间接完成上面这条语句的功能,这也是“间接运算符”名称的由来。

8.声明指针变量时必须指定指针所指向变量的类型, 因为不同的变量类型占用不同的存储空间, 一些指针操作要求知道操作对象的大小。 另外, 程序必须知道储存在指定地址上的数据类型。 long和
float可能占用相同的存储空间, 但是它们储存数字却大相径庭。 下面是一些指针的声明示例:
int * pi; // pi是指向int类型变量的指针
char * pc; // pc是指向char类型变量的指针
float * pf, * pg; // pf、 pg都是指向float类型变量的指针
类型说明符表明了指针所指向对象的类型, 星号(*) 表明声明的变量是一个指针。 int * pi;声明的意思是pi是一个指针, *pi是int类型

9.C99新增了变长数组(variable-length array, VLA) , 允许使用变量表示数组的维度。 如下所示:
int quarters = 4;
int regions = 5;
double sales[regions][quarters]; // 一个变长数组(VLA)
前面提到过, 变长数组有一些限制。 变长数组必须是自动存储类别, 这意味着无论在函数中声明还是作为函数形参声明, 都不能使用static或extern存储类别说明符(第12章介绍) 。 而且, 不能在声明中初始化它们。 最终,C11把变长数组作为一个可选特性, 而不是必须强制实现的特性。注意 变长数组不能改变大小变长数组中的“变”不是指可以修改已创建数组的大小。 一旦创建了变长数组, 它的大小则保持不变。 这里的“变”指的是: 在创建数组时, 可以使用变量指定数组的维度。

10.数组是一组数据类型相同的元素。 数组元素按顺序储存在内存中, 通过整数下标(或索引) 可以访问各元素。 在C中, 数组首元素的下标是0, 所以对于内含n个元素的数组, 其最后一个元素的下标是n-1。 作为程序员, 要确保使用有效的数组下标, 因为编译器和运行的程序都不会检查下标的有效性。声明一个简单的一维数组形式如下:
type name [ size ];
这里, type是数组中每个元素的数据类型, name是数组名, size是数组元素的个数。 对于传统的C数组, 要求size是整型常量表达式。 但是C99/C11允许使用整型非常量表达式。 这种情况下的数组被称为变长数组。C把数组名解释为该数组首元素的地址。 换言之, 数组名与指向该数组首元素的指针等价。 概括地说, 数组和指针的关系十分密切。 如果ar是一个数组, 那么表达式ar[i]和*(ar+i)等价。
对于 C 语言而言, 不能把整个数组作为参数传递给函数, 但是可以传递数组的地址。 然后函数可以使用传入的地址操控原始数组。 如果函数没有修改原始数组的意图, 应在声明函数的形式参数时使用关键字const。 在被调函数中可以使用数组表示法或指针表示法, 无论用哪种表示法, 实际上使
用的都是指针变量。
指针加上一个整数或递增指针, 指针的值以所指向对象的大小为单位改变。 也就是说, 如果pd指向一个数组的8字节double类型值, 那么pd加1意味着其值加8, 以便它指向该数组的下一个元素。
二维数组即是数组的数组。 例如, 下面声明了一个二维数组:
double sales[5][12];
该数组名为sales, 有5个元素(一维数组) , 每个元素都是一个内含12个double类型值的数组。 第1个一维数组是sales[0], 第2个一维数组是sales[1], 以此类推, 每个元素都是内含12个double类型值的数组。 使用第2个下标可以访问这些一维数组中的特定元素。 例如, sales[2][5]是slaes[2]的
第6个元素, 而sales[2]是sales的第3个元素。
C 语言传递多维数组的传统方法是把数组名(即数组的地址) 传递给类型匹配的指针形参。 声明这样的指针形参要指定所有的数组维度, 除了第1个维度。 传递的第1个维度通常作为第2个参数。 例如, 为了处理前面声明的sales数组, 函数原型和函数调用如下:
void display(double ar[][12], int rows);

display(sales, 5);
变长数组提供第2种语法, 把数组维度作为参数传递。 在这种情况下,对应函数原型和函数调用如下:
void display(int rows, int cols, double ar[rows][cols]);

display(5, 12, sales);
虽然上述讨论中使用的是int类型的数组和double类型的数组, 其他类型的数组也是如此。 然而, 字符串有一些特殊的规则, 这是由于其末尾的空字符所致。 有了这个空字符, 不用传递数组的大小, 函数通过检测字符串的末尾也知道在何处停止。

哈姆雷特,请继续前行!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值