《C和指针》总结

《C和指针》

第一章 快速上手

1.1 简介
1.1.1 空白和注释

空行将程序的不同部分分隔开来;制表符用于缩进语句(Tab),以更好的显示程序的结构。C是一种自由格式的语言,并没有规则要求你必须怎样书写语句,但是如果在编程中能够遵守一些约定还是非常值得的,它可以使得代码更加容易阅读和修改。

在C语言中,注释有两种风格,分别为:

  • //
  • //…

在C程序中凡是可以插入空白的地方都可以插入注释,但是,注释不能嵌套。也就是说,第一个/符号和第一个/符号之间的内容全部被看成是注释,不管里面还有多少个*/符号

在有些语言中,注释有时用于把一段代码注释掉,也就是这段代码在程序中不起作用,但并不将其真正从源文件中删除。在C语言中,这并不是一个好主意,如果你试图在一段代码的首尾分别加上//来注释掉这段代码,我们是不一定能如愿的。如果这段代码内部原来就有注释在,这样做就不会出现问题,要从逻辑上删除一段C代码,更好的方法是使用#if指令,只要像下面这样使用就可以了

#if 0
    statements
#endif

在#if和#endif之间的程序段就可以有效的从程序中去除,即使这段代码之间原来存在注释也无妨,所以说这是一种更为安全的方法。

1.1.2 预处理指令
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAX_COLS 20

上面这些称为预处理指令,因为他们是由预处理器解释的。预处理器读入源代码,根据预处理指令对其进行修改,然后把修改过的源代码递交给编译器。

在我们的例子程序中,预处理器用名叫stdio.h的库函数头文件的内容替换第一条#include指令语句,其记过就是仿佛是stdio.h的内容被逐字写到源文件的那个位置,第2,3条的指令功能类似。

提示:

  • 如果你有一些声明需要用几个不同的源文件,这个技巧也是一种方便的方法——你在一个单独的文件中编写这些声明,然后用#include指令把这个文件包含到需要使用这些声明的源文件中。这样,就只需要这些声明的一份拷贝,用不着在许多不同的地方进行复制。避免了在维护这些代码时出现错误的可能性。
  • 另一种预处理指令是#define,他把名字MAX_COLS定义为20.当这个名字以后出现在源文件的任何地方时,他就会被替换为定义的值。由于他们被定义为字面值常量,所以这些名字都不能出现于有些普通变量可以出现的场合(比如赋值符号的左边)。#define指令和其它语言中符号常量的作用类似,可以做到一改全改。

函数由返回值类型,函数名和函数参数组成。

提示:

  • 假如这个程序的源代码由几个源文件组成,那么使用该函数的源文件都必须写明该函数的原型。把函数原型放在头文件中并使用#include指令包含他们,可以避免由于同一个声明的多分拷贝而导致的维护性问题。
1.1.3 main函数

每个C程序都必须有一个main函数,因为他是程序执行的起点。

扫描二维码关注公众号,回复: 11142564 查看本文章

在C语言中,数组参数是以引用形式进行传递的,也就是传址调用,而标量和常量则是按值传递的。在函数中对标量参数的任何修改都会在函数返回时丢失,因此,在调用函数无法调用函数以传值形式传递给它的参数。然而,当被调用函数修改数组参数的其中一个元素时,调用函数所传递的数组就会被实际的修改。

事实上,关于C函数的参数传递规则可以表述如下:

所有传递给函数的参数都是按照值传递的。但是,当数组名作为参数时就会产生按引用传递的效果

常用的printf格式代码:

  • %d 以十进制形式打印一个整型值
  • %o 以八进制形式打印一个整形值
  • %x 以十六进制形式打印一个整型值
  • %c 打印一个字符
  • %s 打印一个字符串
  • \n换行
1.1.4 read_colum_numbers 函数
int read_colum_numbers(int columns[],int max)

注意:这个声明和早先出现在程序中的该函数原型的参数个数和类型以及函数的返回值完全匹配,如果出现不匹配的话,编译器就会报错

在函数声明的数组参数中,并未指定数组的长度,这种格式是正确的,因为不论调用函数的程序传递给它的数组参数的长度是多少,这个函数都将照收不误。这是一个伟大的特性,它允许单个函数操纵任意长度的一维数组。这个特性不利的一面是函数没法知道该数组的长度。如果确实需要数组的长度,他的值必须作为一个单独的参数传递给函数。

标准并未影星规定C编译器对数组下标的有效性进行检查,而且绝大多是C编译器确实也不进行检查。因此,如果需要进行数组下标有效性的检查的话,必须自行编写代码。

&&是逻辑与操作符,要使得表达式的值为真,&&操作符两边的表达式必须都为真。然而,如果左边的表达式为假,右边的表达式就不再进行求值,因为其不管是真是假,整个表达式总是假的。

ch=getchar();
while(ch!=EOF&&ch!='\n')
  ch=getchar();

一个经常问的的问题是:为什么ch被声明为整形,而我们事实上需要他来读取字符?答案是EOF是一个整型值,他的位数比字符类型要多,把ch声明为整形可以防止输入读取字符意外地被解释为EOF,同时这也意味着,接收字符的ch必须足够大,足以容纳EOF,这就是ch使用整形的原因。字符只是小整数而已,所以用一个整型变量容纳字符值并不会引起任何问题。

1.1.5 rearrange函数

for循环:

for语句包含三个表达式(顺便说一下, 这三个表达式都是可选的)。第一个表达式是初始部分,他只在循环开始前执行一次。第二个表达式时测试部分,他在循环每执行一次后都要执行一次。第三个表达式是调整部分,他在每次循环执行完毕后都要执行一次,但是他咋测试部分之前执行。

1.2 补充说明

本章的例子程序描述了许多C语言的基础知识,但是我们还应该知道一些东西。首先是putchar函数,他与getchar函数相对应,它接受一个整形参数,并在标准输出中打印该字符(如前所述,字符的本质上也是整数)

1.3 编译
1.4 总结

本章的目的是描述足够的C语言基础知识,让你对C语言有一个整体的印象,有一些基础。

1.5 警告的总结
  • 在scanf函数的标量惨啊书前未添加&操作符
  • 机械地把printf函数地格式代码照搬于scanf函数
  • 在应该使用&&操作符地地方误用了&操作符
  • =和==误用
1.6 编程提示的总结
  • 使用#include指令避免重复声明
  • 使用#define指令给常量取名
  • 在使用下标前先检查他们的值
  • 在while和if表达式中蕴含赋值操作
  • 始终要进行检查,确保数组不越界

第二章 基本概念

2.1 环境

在ANSIC的任何一种实现中,存在两种不同的环境,第一种时翻译环境,在这个环境里,源代码被转换为可执行的机器指令。第二种时执行环境,他用于实际执行代码。这两种环境不必位于同一台机器上。

2.1.1 翻译

翻译阶段由几个步骤组成,组成一个程序的每个(有可能是多个)源文件通过比那一过程分别转换为目标代码(object code)然后,各个目标文件由连接器捆绑在一起,形成一个单一而完整的可执行程序。链接器同时也会引入标准C函数库种任何被该程序所用到的函数,而且它也可以搜索程序员个人的程序库。

编译过程本身也有几个阶段组成,首先是预处理器处理。在这个阶段,预处理器在源代码上执行一些文本操作。例如,用实际值代替由#define指令定义的符号以及读入由#include指令包含的文件的内容。

然后,源代码经过解析,判断它的语句的意思。第二个阶段是产生绝大多数错误和警告信息的地方,随后,便产生目标代码。目标代码是机器指令的初步形式,用于实现程序的语句。如果我们在编译程序的命令行种加入了要求进行优化的选项,优化器就会对目标代码进一步进行处理,使得效率更高。优化过程需要额外的时间,所以在程序调试完毕并准备生成正式产品之气那一般不进行这个过程。

一. 文件名约定:
  • 尽管标准并没有指定文件的取名规则,但大多数环境都存在你必须遵守的文件名命名约定。C源代码通产保存于以.c扩展命名的文件中。由#include指令包含到C源代码的文件称为头文件,通常是.h。针对于目标文件,在unix系统中,他的扩展命为.o,在MS-DOS系统中,他们的拓展名为.obj
二. 编译和链接:

用于编译和链接C程序的它特定命令在不同的系统中各不相同,但许多都和这里所描述的两种系统差不多。在绝大多数UNIX系统中,C编译器被称为cc,它可以用多种不同的方法来调用

  • 编译并连接一个完全包含于一个源文件的C程序
cc program.c

这条命令产生一个称为a.out的可执行程序。中间会产生一个名为program.o的目标文件,但他在链接过程完成之后会被删除

  • 编译并链接结构C源文件
cc main.o lookup.o sort.c
  • 编译一个C源文件,并把他和现存的目标文件链接在一起
 cc main.o lookup.o sort.c
  • 编译单个C源文件,并产生一个目标文件(本例子中位program.o),以后再进行链接
cc -c program.c
  • 编译几个C源文件,并且每个文件产生一个目标文件
cc -c main.c sort.c lookup.c

Windows集成开发环境是一个完整的独立编程工具,它包括源代码编辑器,调试器和编译器,他的具体使用不再本书的范围之内

2.1 执行

程序的执行过程也需要经历几个阶段。首先,程序必须载入再内存之中,在宿主环境中(也就是具有操作系统的环境),这个任务由操作系统完成。哪些不是存储在堆栈中尚未初始化的比那辆将在这个时候得到初始值。在独立环境中,程序的载入必须由手工安排,也可能是通过把可执行代码置入只读内存中来完成。

然后,程序就开始执行了。在宿主环境中,通常一个小型的其中程序与程序链接在一起。它负责处理一系列日常事物,如收集命名行参数以便程序可以访问他们。接着,调用main函数。

现在便开始执行程序代码,在绝大多数机器里,程序将使用一个运行时的堆栈,它用于存储函数的局部变量和返回地址。程序同时也可以使用静态内存,存储于静态内存中的变量在整个程序执行工程中将一直保留他们的值。

程序执行的最后一个阶段就是程序的终止,它可以由多种不同的原因引起,正常终止就是main函数返回,有些执行环境允许程序返回一个代码,提示程序为什么会停止执行

2.2 词法规则

此法规则就像英语中的拼写规则,决定你在源程序中如何形成单独的字符片段,也就是标记。

一个ANSIC程序由声明和函数组成。函数定义了需要执行的工作,而声明则描述了函数和函数将要操作的数据类型(有时候是数据本身)。注释可以散布于源文件的各个地方

2.1.1 字符

标准规定字符集必须包括英语所有的大写和小写字母,数字0-9.以及一些符号

ANSIC在基础之上有增加了几个转义序列。转移序列由一个反斜杠\加上一个或多个其他字符组成。下面列出的每个转义序列代表发斜杠后面的那个字符,但并未给这个字符增加特别的意义

  • \? 在书写连续多个问好时使用,防止他们被解释成三字母词
  • " 用于表示一个字符串常量内部的双引号
  • ’ 用于表示字符常量’
  • \ 用于表示一个反斜杠,防止他被解释成一个转义序列符
    C语言还一共了一些其他的转义字符
  • \a 出发蜂鸣器
  • \b 退格键
  • \t 水平制表符
  • \ddd 八进制数字
  • \xddd 16进制数字
2.2.2 注释

注释从注释起始符开始,到注释终止符结束,期间所有的东西均作为注释的内容

2.2.3 自由形式的源码
2.2.4 标识符

标识符就是变量,函数,类型的名字。它们由大小写字母,数字和下划线组成,但是不能以数字开头。C语言是一种大小写敏感的语言,所以abc,ABC,Abc时不同的标识符。标识符的长度没有限制,但标准允许编译器忽略31个字符以后的字符。标准同时允许编译器对用于表示外部名字(也就是链接器操纵的名字)标识符进行限制,只识别前六位不区分大小的。

2.2.5 程序的形式

一个C程序可能保存于一个或多个源文件中,虽然一个源文件可以包含超过一个的函数,但每个函数都必须完整的出现于同一个源文件中。

2.3 程序的风格

列出风格的几个特性:

  • 空行用于分隔不同的逻辑代码段,他们是按照功能分段的。
  • if和相关语句的括号是这些语句的一部分,而不是他们所测试的表达式的一部分,所以,在括号和表达式之间留下一个空格,使得表达式看上去突出一些,函数的原型也是如此。
  • 在操作符的使用中,中间要隔出一个空格
  • 嵌套于其他语句的语句将缩进
2.5 警告的总结
  • 字符串常量中的字符被错误的解释为三字母词
  • 编写的糟糕的注释可能会意外的终止语句
  • 注释的不适当结束

第三章 — 数据

程序对数据进行操作,本章将对数据进行描述。描述他的各种类型,描述他的特点以及如何声明它。本章还将描述变量的三个属性:作用域,连接属性和存储类型。这三个属性决定了一个变量的"可视性"(也就是它可以在什么地方使用)和"生命期"(他的值将保持多久)

3.1 基本数据类型

C中,有四种类型——整形,浮点型,指针和聚合类型(如数组和结构等)

3.1.1 整形家族

听上去长整形所能表示的值应该比短整型所能表示的值要打,但这个假设并不一定正确。规定整型值相互之间大小的规则很简单:长整型至少应该和整形一样长。

标准被没有规定长整型必须比短整型长,只是规定不得比短整型短。下为变量的范围

  • char 0~127
  • signed char -128~127
  • unsigned char 0~255
  • short int -32768~32767
  • unsigned int 0~65535
  • long int -2147483648~2147483467
  • unsigned int 0~4294967295

short int 至少16位,long int 至少32位。至于缺省的int究竟是16位还是32位,或者是其他的值,则由编译器设计者决定。通常这个选择的缺省值是这种机器最为自然(高效的位数),同时应该还要注意标注并没有规定这三个值必须不一样。如果某种机器的环境的字长是32位,而且没有什么指令能够有效的处理更短的整形,它可能吧这三个整形的值都定为32位。

头文件limits.h说明了各种不同的整形类型的特点。

尽管设计char类型变量的目的是为了让他们容纳字符型值,但字符本质上是小整形值。缺省的char要么是signed char,要么是unsigned char ,这取决于编译器。这个事实意味着不同机器上的char可能有用不同范围的值。所以,只有当程序的所使用的char型变量的值位于signed char和unsigned char 的交集中,这个程序才是可移植的。

提示:

  • 当可移植问题比较重要时,字符是否为有符号数就会带来两难的境地。最佳妥协方案就是把存储于char型变量的值限制在signed char和unsigned char 的交集内。这可以获得最大程度的可移植性。
一.整形字面值

字面值这个术语是字面值常量的缩写——这时一种实体,制定了自身的值,并且不允许发生改变,这个特点非常重要,因为ASCII允许命名常量(声明为const的变量)的创建,他与普通变量及为相似。区别在于,当他被初始化以后,他的值不能再改变了。

在源代码中,用于表示整形字面值方法有很多。其中最自然的方式就是十进制整数,诸如:123 65535 等等等等…

十进制整形字面值可能是int,long,或unsigned long。在缺省情况下,他是最短类型但能完整容纳这个值。

整数也可以用八进制来表示,只要在数值前面以0开头。整数也可以用16进制来表示,它以0x开头。

在八进制字面值中,数字8和9是非法的。在十六进制里面,可以使用ANCDEF或abcdef。八进制和十六进制字面值可能的类型是int,unsigned int long或unsigned long。在缺省情况下,字面值的类型就是上述类型中最短但组抑容纳整个值的类型。

另外还有字符常量,他们的值总是int

二.枚举类型

枚举类型就是指他的值为符号常量而不是字面值的类型,他们以下面这种形式声明:

enum Jar_type{CUP,PINT,QUART,HALT_GALLO};

这条语句声明了一个类型,称为Jar_type。这种类型的变量按下列方式声明:

enum Jar_type milk_jug,gas_can.medicine_bottle;

如果某特别的枚举类型只使用一个声明,你可以把上面两条语句组合成下面的这种样子:

enum {CUP,PINT,QUART,HALT_GALLO}
    milk_jug,gas_can.medicine_bottle;

这种类型的变量实际上是以整形的方式存储,这些符号的实际值都是整形值。这里CUP为0,PINT为1,以此类推。适当的时候,可以为这些符号名指定特定的值。

提示:符号名被当作整形常量处理,声明为枚举类型的变量实际上是整数类型。这个事实意味着你可以给Jar_type类型的变量赋字面值。但是,避免用这种方式来使用枚举,因为把枚举常量同整数无差别的混合在一起使用,会削弱他们的值得含义。

3.1.2 浮点类型

浮点数家族包括float,double,long double类型。通常,这些类型分别提供单精度,双精度以及在某些支持扩展精度得机器上提供扩展精度。ANSI标准仅仅规定long double至少和double一样长,而doble至少和float一样长。

头文件float.h提供了浮点型所能存储的最大值。

浮点数字面值在缺省情况下都是double类型的,除非它后面给一个f表示他是float类型的。

3.1.3 指针

变量的值存储在计算机内部当中,每个变量都占据一个特定的位置。每个内存位置都由地址唯一确定并以农业那个,就像一条街道上的房子由他们的门派号码标识一样。指针只是地址的另一个名字罢了。指针变量就是一个其值为另一些内存地址的变量。

一.指针常量

指针常量和非指针常量在本质上是不同的,编译器复杂把变量赋值给计算机内存中的位置,程序员事先无法知道某个特定的比那辆将存储在内存中的拿个位置,因此,需要操作符来获得一个变量的地址而不是直接把他的地址写成字面值常量的形式。

二.字符串常量

许多人对C语言不存在字符串类型感到奇怪,不过C语言提供了字符串常量。事实上,C语言存在字符串的概念:它就是遗传以NUL字节结尾的0个或者多个字符。字符串通常存储在字符数组中,这也是C语言美哦与显示字符串类型的原因。因为NUL字节适用于终结字符串的,所以在字符串内部不能有NUL字节。之所以选择NUL作为字符串的终止符,是因为他是一个不可以打印的字符。字符串常量的书写方式是用一对双引号包围的字符。字符串常量可以是空的。

ANSIC编译器不允许修改字符串常量,或者提供编译时选项,让你自行选择是否允许修改字符串常量,在实践中,请尽量避免这样做。如果你需要修改字符串,请把它存储于数组中。

之所以把字符串常量和指针放在一起讨论,是因为在程序中使用字符串常量会生成一个指向字符的常量指针。当一个字符串常量经常出现于一个表达式中,表达式所使用的值就是这些字符所存储的地址,而不是这些字符本身。因此,可以把字符串常量赋值给一个指向字符的指针,后者指向这些字符所存储的地址。但是,不能把字符串常量赋值给一个字符数组,因为字符常常量的直接值是一个指针,而不是这些字符本身。

3.2 基本声明

变量声明的基本形式是: 说明符(一个或者多个) 声明表达式列表

对于简单的类型,声明表达式列表就是被声明的标识符列表。对于更为复杂的类型,声明表达式列表中的每个条目实际上是一个表达式,显示被声明的名字的可能用途。

说明符包含了一些关键字,用于描述被声明的标识符的基本类型。说明符也可以用于改变标识符的缺省存储类型和作用域。

基本变量的声明

int i;
char j,k,l;

说明符也可以用于一些修改变量的长度或是否为有符号数的关键字,这些关键字是:

short long signed unsigned

signed关键字一般只用于char类型,因为其他整形类型在缺省情况下都是有符号数,至于char是否为signed,则因编译器而异,所以char可能等同于signed char,也可能等同于unsigned char

3.2.1 初始化

在一个声明中,你可以给一个标量指定一个初始值,方法是在变量名后面加一个符号(赋值号)后面就是你想要赋给变量的值。

int i = 15;
3.2.2 声明简单数组
int values[20];

对于这个声明的解释是:我们声明了一个数组,数组包含20个整形元素。数组的下标总是从0开始,最后一个元素的下标是元素的数目-1.

C数组另一个值得关注的地方是,编译器并不缉拿查程序对数组下标的引用是否在数组的合法范围之内。这种不检查的行为有好处也有坏处。好处是不需要浪费时间对有些已知是正确的数组下标进行检查。坏处是这样做将使无效的下标引用无法被检测出来。一个良好的检测法则是:如果下标值是从哪些已知是正确的值产生而来的,那么就无需检查他们的值。如果一个用作下标的值是根据某种方法v哦那个用户输入的数据产生来的,那么在使用它之前就必须进行检测,确保位于有效的范围之内。

3.2.3 声明指针
int *a

这条语句表达式a产生的结果类型是int。知道了操作符执行的是简介访问操作以后,我们可以推断a肯定是一个指向int的指针。

在声明指针变量时,你也可以为他指定初始值,这里有一个例子,它声明了一个指针,并用一个字符串常量对其进行初始化

char *message ="hello world";

这条语句把message作为一个指向字符的指针,并用字符串常量中的第一个字符的地址对该指针进行初始化。

警告:

这种类型声明所面临的一个危险是你容易误解他的意思。在前面一个声明中,看上去初始值似乎是赋给表达式*message,事实上他是赋给message本身的。换句话说,前面一个声明相当于

char *message;
message = "hello world"
3.2.4 隐式声明

C语言中有几种声明,他的类型名可以省略。例如,函数如果不是显示的声明返回值类型,他就默认返回值类型为整形。

3.3 typedef

typedef允许你为各种数据类型定义新的名字,typedef关键字要出现在声明的前面。

使用typedef声明类型可以减少使声明变长的危险,尤其是那些复杂的声明

提示:你应该使用typedef而不是#define来创建新的类型名,因为后者无法正确的处理指针类型

3.4 常量

ANSIC允许你声明常量,常量的样子和变量一样,只是他们的值不能修改,可以使用const关键字来声明常量

int const a;
const int a;

这两条语句都把a声明为一个整数,他的值不能被修改。当然,由于a的值无法被修改,所以你无法把任何东西赋值给它。如此一来,你怎么才能让他在一开始拥有一个值呢?有两种方法:

  • 首先,你可以在声明是对他进行初始化,如下所示:
int const a=15;

其次,在函数中声明为const的形参在函数被调用时会得到实参的值。

当涉及到指针变量时,情况会变得更加有趣,因为有两样东西都有可能成为常量——指针变量和它所指向的实体。

int *pi;

pi是
一个普通的指向整形的指针

int const *pci;

pci则是一个指向整形常量的指针。你可以修改指针的值,但是不能去修改它所指向的值,相比之下

int *const cpi

则声明cpi为一个指向整形的常量指针,此时指针式常量,他的值无法修改,但是你可以修改他所指向的整形的值。

int const *const cpci

这个例子中,无论是指针变量本身还是它所指向的值,都是不可以被修改的。

提示:

  • 当你声明变量时,如果变量的值不会被修改,应该在声明中使用const关键字。
3.5 作用域

当变量在程序的某个部分被声明时,他只有在程序的一定区域才能被访问到。这个区域由标识符的作用域决定。标识符的作用域就是程序中该标识符可以被使用的区域。

编译器可以确认四种不同类型的作用域——文件作用域,函数作用域,代码作用域,和原型作用域。

3.5.1 代码块作用域

位于一对花括号之间的所有语句称为一个代码块。任何在代码块的开始位置声明的标识符都具有代码块作用域,表示他们可以被这个代码块中的所有语句访问。

当代码块处于嵌套状态时,声明于内层代码块的标识符的作用域到达该代码块的尾部便告终止。然而,如果内层代码块有一个标识符的名字与外层代码块的一个标识符同名,内层的那个标识符就将隐藏外层标识符——外层的那个表示符就无法在内层代码块中通过名字访问。

提示:

  • 应当避免在嵌套的代码块中出现相同的变量名

不是嵌套的代码块则稍有不同。生命与每个代码块的变量无法被另一个代码块访问,因为他们的作用域并没有重叠的地方。

3.5.2 文件作用域

任何在代码块之外声明的表示符都具有文件作用域,它表示这些标识符从他们的声明之处知道它所在的源文件夹结尾处都是可以访问的。

3.5.3 原型作用域

原型作用域直只适用用于函数原型中声明的参数名。在原型中参数的名字非必需。但是,如果出现参数名,你可以随你所愿给他们取任意的名字,他们不必与函数定义中的形参名匹配,也不必与函数实际调用时所传递的实参匹配。

3.5.4 函数作用域

简化为一条规则——函数中的所有语句标签必须唯一

3.6 链接属性

标识符的链接属性决定如何处理在不同文件中出现相同的标识符,标识符的作用域与他的链接属性有关,但是这两个属性并不相同。

链接属性一共有三种——external(外部),internal(内部)和none(无)。没有链接属性的标识符总是被当成是单独的个体,也就是说该标识符的多个声明被当作独立于不同的实体。属于internal链接属性的标识符在同一个源文件内的所有声明中都指向同一个实体,但位于不同源文件的多个声明则分属不同的实体。最后,属于external连接属性的标识符不论声明多少次,位于几个源文件都表示同一个实体。

关键字extern和static用于在声明中修改标识符的连接属性。如果某个声明在正常情况下具有external链接属性,在他前面加上static关键字可以使它的链接属性变为internal。

3.7 存储类型

变量的存储类型是指存储变量的内存类型。变量的存储类型后决定变量什么时候创建和销毁以及他的值将会保持多久。有三个地方可以用于存储变量:普通内存,运行时堆栈,硬件寄存器。在这三个地方存储的变量具有不同的特性。

变量的缺省存储类型取决于他的声明位置,凡是在任何代码块之外声明的变量总是存储与静态内存中,也就是不属于堆栈的内存,这类变量称为静态变量。对于这类变量,你无法为他们指定其他存储类型。静态变量在程序运行之前创建,在程序的整个执行期间内始终存在,他始终保持原先的值,除非给他赋一个不同的值或者程序结束。

在代码块内部声明的变量的缺省存储类型是自动的,也就是说它存储于堆栈中,称为自动变量(auto)变量。有一个关键字auto就是用于修饰这种存储类型的,但是他极少用,因为代码块中的变量在缺省情况下就是自动变量。程序结束的时候自行销毁掉。当代码块再次执行时,他的值一般并不是上次执行的值。

对于在代码块内部声明的变量,如果给他加上关键字static,可以使它的存储类型从自动变为静态。具有静态存储类型的变量在整个程序执行过程中已知存在,而不仅仅在声明他的代码块的执行时存在。注意,修改变量存储类型并不表示修改变量的作用域,他仍然只能在该该骂快内部按名字访问。函数的形式参数不能声明为i静态,因为实参总是在堆栈中传递给函数,用来支持递归。

关键字register可以用于自动变量的声明,提示他们应该存储于机器的硬件寄存器而不是内存中,这类变量称为寄存器变量。通常寄存器变量必存储于内存的变量访问起来效率高。但是,编译器不一定理睬register关键字,如果有太多变量被声明为register,他只能选取前几个实际存储于寄存器中,其余的按照普通自动变量处理。在计算机中,如果把指针声明为寄存器变量,指针的效率将能得到提高,尤其是那些频繁执行间接访问操作的指针。

初始化

自动变量和静态变量的初始化存在一个重要的差别。在静态变量的初始化中,我们可以把可执行程序文件想要初始化的值放在当程序执行时变量将会使用的位置。当可执行文件载入到内存是,这个已经保存了正确初始值的位置将赋值给那个变量,这个任务不需要额外的时间。也不需要额外的指令,变量就会的到正确的值。如果不显示的指定其初始值,静态变量将初始化为0

3.8 static关键字

当用于不同的上下文环境是,static关键字具有不同的意思。

当他用于函数定义时,或用于代码块之外的变量声明式,static关键字用于修改标识符的链接属性,从external变为internal,但标识符得存储类型和作用域不受影响,用这种方式声明得函数或者变量只能在声明他们的源文件内部适用

当他用于声明代码块内部的变量时,static关键字用于修改变量的存储类型,从自动化变量修改为静态变量,但变量的链接属性和作用域不受影响,用这种方式声明的变量在程序执行之前创建,并在程序的整个执行期间一直存在,而不是每次在代码块开始执行时创建,在代码块执行完毕后销毁。

3.10 总结

具有external链接属性的实体在其他语言的术语力称为全局实体,所有源文件中的所有函数都可以访问它。只要变量并非声明于代码块的函数定义内部,他在缺省情况下的连接属性即为external.如果一个变量声明于代码块内部,在他前面添加extern关键字将使它所引用的时全局变量而不是局部变量。

具有external链接属性的实体总是具有静态存储类型。全局变量在程序开始执行前创建,并在程序整个执行过程中始终存在。从属于函数的局部变量在函数开始执行时创建,在函数执行完毕后销毁,但用于执行函数的机器指令在程序的生命期内一直存在。

3.12 编程提示的总结
  • 为了保持最佳的可以执行,把字符的值限制在有符号和无符号字符范围的交集之内,或者不要再字符上执行算数运算
  • 不要把整型值和枚举值混在一起适用
  • 再定义类型的新名字时,使用typedef而不是#define
  • 用const声明其值不会修改的变量

第四章 — 语句

C语言实现了其他高级语言所具有的所有语句。if语句用于在几段备选码中选择运行其中的一段,而while,for和do语句则用于实现不同类型的循环。

但是,和其他语言相比,C语言还是存在一些不同之处。例如,C语言并不具有专门的赋值语句,而是统一用表达式语句代替。switch语句实现了其他语言中case语句功能,但是其实现的方式却非比寻常。

4.1 空语句

C语言最简单的就是空语句,它本身只包含有一个分号。空语句本身并不执行任何任务,但有时还是有用。它所使用的场合就是语法要求出现一条完整的语句,但是不需要它执行任何任务。

4.2 表达式语句

既然C没有专门的赋值语句,那么它如何进行赋值呢。答案是赋值就是一种操作,就像加法减法一样,所以赋值就在表达式内进行。

你只要在表达式后面加上一个分号,就可以把表达式转变为语句。

考虑下面这个语句

printf("hello world");

printf是一个函数,函数将会返回一个值,但printf函数的返回值(返回值是打印的字符数)我们通常不关心,所以不理也没什么。所谓语句没有效果至少表达式的值被忽略,printf函数执行的是有用的工作,这类作用被称为副作用

4.3 代码块

代码块就是位于一对花括号之内的可选的声明和列表语句,代码块可以用于要求出现语句的地方。

4.4 语句
if(expression)
   statement
else
   statement

括号是if语句的一部分,而不是表达式的一部分,因为他必须是必须出现的。

如果expression值为真,那么执行第一个statement,否则就跳过他。

在C的if语句和其他语言的if语句中,只存在一个差别,C并不具备bool类型,而是用整形来代替。这样expression可以是任何能够产生整形结果的表达式——零值表示假,非0值表示真。

悬挂else

else总是于离它最近的未配对的if语句进行配对。

4.5 while语句
4.5.1 break和continue语句

while语句中使用break语句,用于永久终止循环。在执行完break语句之后,执行流下一条执行的语句就是循环正常结束后应该执行的那条语句。

while语句中使用continue语句,它用于永久终止当前的那次循环。在执行完continue语句之后,执行流接下来就是重新测试表达式的值,决定是否继续执行循环。

这两条语句的任何一条如果出现于嵌套循环的内部,他只对内层循环起作用,无法使用break或者continue语句影响外层循环的执行。

4.5.2 while语句的执行过程
4.6 for语句

C的for语句是while循环的一种极为常用的语句组合形式的简写法

for语句有三大部分:初始化部分,条件部分,调整部分,所有的三个表达式都是可选的,都是可以省略的,如果省略条件部分,表示测试的值始终为真。

在for语句中也可以使用break和continue语句,break语句立即退出循环,而continue语句把控制流直接转移到调整语句

提示:for循环有一个风格上的优势,它把所有用于操纵循环的表达式收集在一起,放在同一个地点,便于寻找

4.7 do语句

这种循环至少会执行一次。

我们如何在while语句和do语句之间进行选择呢?当你需要循环体至少执行一次的时候,选择do语句。

4.8 switch语句
switch(expression)

其中,expression的结果必须是整型值。每个case标签必须具有一个唯一的值。常量表达式是指在编译期间进行求值的表达式,它不能是任何变量。

switch执行流将贯穿整个case标签,而不是停留在单个case标签,这也是为什么case标签只是确定语句列表的进入点而不是划分他们的图案因。如果你觉得这个行为看上去不那么正确,有一种方法可以纠正,那就是break语句

4.8.1 switch中的break语句

break语句的实际效果是把语句划分为不同的部分。

4.8.2 default子句

在没有匹配的情况下,会跳转到default子句。

4.9 goto语句
goto 语句标签

要使用goto语句,你必须在你希望跳转的语句前面加上语句标签,语句标签就是标识符后面加上个冒号。

goto语句是一个危险的语句,因为在学习C的过程中,容易对他形成依赖,所以说,还是少用goto语句

第五章 — 操作符和表达式

发布了80 篇原创文章 · 获赞 84 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43831728/article/details/104537195
今日推荐