• C复习


    第1章程序设计和C语言……1
    1.1什么是计算机程序…1
    1.2什么是计算机语言……1
    1.3C语言的发展及其特点.3
    1.4最简单的C语言程序…5
    1.4.1最简单的C语言程序举例……6
    1.4.2 C语言程序的结构……10
    1.5运行C程序的步骤与方法……12
    1.6程序设计的任务…14

    #include <stdio.h>
    
    int main() {
        printf ("This is a C program.
    ");
        return 0;
    }
    /**
     * 在使用函数库中的输入输出函数时,编译系统要求程序提供有关此函数的信息(例如对这些输入输出函数的声明和宏的定义、全局量的定义等),程序第1行“#include <stdio.h>”
     *
     */
    
    
    #include <stdio.h>
    
    int main() {
        int a,b,sum;
        a=123; b=456;
        sum=a+b;
        printf ("sum is %d
    ", sum);
        return 0;
    }
    /**
     *
     */
    
    
    #include <stdio.h>
    void main ( ){
        int max(int x,int y);
        int a, b, c;
        scanf ("%d,%d",&a,&b);
        c=max (a,b);
        printf ("max=%d",c);
    }
    
    int max(int x,int y){
        int z;
        if (x>y) z=x;
        else z=y;
        return (z);
    }
    
    C:UsersadminiDownloadsCTanHaoQiangcmake-build-debugCTanHaoQiang.exe
    1,2
    max=2
    Process finished with exit code 5
    
    
    对源程序进行编译,先用C编译系统提供的“预处理器”(又称“预处理程序”或“预编译器”)对程序中的预处理指令进行编译预处理。例如,对于#include<stdio.h>指令来说,就是将stdio.h头文件的内容读进来,取代#include<stdio.h>行。由预处理得到的信息与程序其他部分一起,组成一个完整的、可以用来进行正式编译的源程序,然后由编译系统对该源程序进行编译。
    
    进行连接处理。经过编译所得到的二进制目标文件(后缀为.obj)还不能供计算机直接执行。前面已说明:一个程序可能包含若干个源程序文件,而编译是以源程序文件为对象的,一次编译只能得到与一个源程序文件相对应的目标文件(也称目标模块),它只是整个程序的一部分。必须把所有的编译后得到的目标模块连接装配起来,再与函数库相连接成一个整体,生成一个可供计算机执行的目标程序,称为可执行程序(executive program),在Visual C++中其后缀为.exe,如f.exe。
    
    
    程序编译链接原理
    预处理:.c -> .i	
    gcc -E  hello.c -o hello.i
    
    编译:.i / .c -> .s
    gcc -S hello.i -o hello.s
    
    汇编:.s  -> .o
    gcc -c hello.s -o hello.o
    
    链接:.o  -> 可执行程序app
    gcc hello.o -o app
    

    第2章算法一—程序的灵魂……16
    2.1什么是算法…16

    算法+数据结构=程序
    

    2.2简单的算法举例…17
    2.3算法的特性…21
    2.4怎样表示一个算法…22
    2.4.1用自然语言表示算法…22
    2.4.2用流程图表示算法…22
    2.4.3三种基本结构和改进的流程图…26
    2.4.4用N-S流程图表示算法…28
    2.4.5用伪代码表示算法…31
    2.4.6用计算机语言表示算法…32
    2.5结构化程序设计方法…34

    第3章最简单的C程序设计——顺序程序设计…37
    3.1顺序程序设计举例…37
    3.2数据的表现形式及其运算…39
    3.2.1常量和变量…39

    普通字符,用单撤号括起来的一个字符,如:a',z’,3’,?,’#’。不能写成’ab'
    或12'。请注意:单撇号只是界限符,字符常量只能是一个字符,不包括单撤号。a'和‘A'
    是不同的字符常量。字符常量存储在计算机存储单元中时,并不是存储字符(如a,z,
    #等)本身,而是以其代码(一般采用ASCII代码)存储的,例如字符’a’的ASCII化代码是97,因此,在存储单元中存放的是97(以二进制形式存放)。ASCII字符与代码对照表见附录B。
    
    转义字符,除了以上形式的字符常量外,C还允许用一种特殊形式的字符常量,就是以字符开头的字符序列。例如,前面已经遇到过的,在printf函数中的
    ’它代表一个“换行”符。	'代表将输出的位置跳到下一个tab位置(制表位置),一个tab位置为8列。这是一种在屏幕上无法显示的“控制字符”,在程序中也无法用一个一般形式的字符来表示,只能采用这样的特殊形式来表示。
    常用的以“”开头的特殊字符见表3.1。
    
    表3.1中倒数第2行是一个以八进制数表示的字符,例如101’代表八进制数101的ASCII字符,即'A'(八进制数101相当于十进制数65,从附录B可以看到ASCII码(+进制数)为65的字符是大写字母’A')。12'代表八进制数12(即十进制数的10)的ASCII码所对应的字符“换行”符。表3.1中倒数第1行是一个以十六进制数表示的ASCII字符,如
    x41'代表十六进制数41的ASCII字符,也是’A'(十六进制数41相当于十进制数65)。用表3.1中的方法可以表示任何可显示的字母字符、数字字符、专用字符、图形字符和控制字符。如33’或x1B'代表ASCII代码为27的字符,即ESC控制符。o’或00’是代表ASCII码为0的控制字符,即“空操作”字符,它常用在字符串中。
    

    字符串常量。如boy”,"123”等,用双撤号把若干个字符括起来,字符串常量是双撇号中的全部字符(但不包括双撇号本身)。注意不能错写成CHINA',boy','123'。单撇号内只能包含一个字符,双撇号内可以包含一个字符串。说明:从其字面形式上即可识别的常量称为“字面常量”或“直接常量”。字面常量是没有名字的不变量。
    
    符号常量。用#define指令,指定用一个符号名称代表一个常量。如:
    #define PI3.1416/注意行末没有分号
    
    常变量,const int a=3;表示a被定义为一个整型变量,指定其值为3,而且在变量存在期间其值不能改变。
    
    定义符号常量用#define指令,它是预编译指令,它只是用符号常量代表一个字符串,在预编译时仅是进行字符替换,在预编译后,符号常量就不存在了(全置换成3.1415926了),对符号常量的名字是不分配存储单元的。而常变量要占用存储单元,有变量值,只是该值不改变而已。从使用的角度看,常变量具有符号常量的优点,而且使用更方便。有了常变量以后,可以不必多用符号常量。
    
    3.2.2数据类型…42
    ![](.assets/C语言允许使用的类型.png)
    3.2.3整型数据…44
    ~~~
    

    数据分配4个字节(32位)。在存储单元中的存储方式是:用整数的补码(complement)形式存放。一个正数的补码是此数的二进制形式,如5的二进制形式是101,如果用两个字节存放一个整数,则在存储单元中数据形式如图3.5所示。如果是一个负数,则应先求出负数的补码。求负数的补码的方法是:先将此数的绝对值写成二进制形式,然后对其后面所有各二进位按位取反,再加1。如一5的补码见图3.6。在存放整数的存储单元中,最左面一位是用来表示符号的,如果该位为0,表示数值为正;如果该位为1,表示数值为负。

    sizeof是测量类型或变量长度的运算符。

    在实际应用中,有的数据的范围常常只有正值(如学号、年龄、库存量、存款额等)。为了充分利用变量的值的范围,可以将变量定义为“无符号”类型。可以在类型符号前面加上修饰符unsigned,表示指定该变量是“无符号整数”类型。如果加上修饰符signed,则是“有符号类型”。因此,在以上4种整型数据的基础上可以扩展为以下8种整型数据。

    对无符号整型数据用“%u”格式输出。%u表示用无符号十进制数的格式输出

    	3.2.4字符型数据…47
    	~~~
    由于字符是按其代码(整数)形式存储的,因此C99把字符型数据作为整数类型的一种。
    
    可以看到,以上的字符的ASCII代号码最多用7个二进位就可以表示。所有127个字符都可以用7个二进位表示(ASCII代码为127时,二进制形式为1111111,7位全1)。所以在C中,指定用1个字节(8位)存储一个字符(所有系统都不例外)。此时,字节中的第1位置为0。
    
    如小写字母’a’在内存中的存储情况见图3.9(a'ASCII代码是十进制数97,二进制数为01100001)。
    
    注意:字符1'和整数1是不同的概念,字符1’只是代表一个形状为’1’的符号,在需要时按原样输出,在内存中以ASCII码形式存储,占1个字节,见图3.10(a),而整数1是以整数存储方式(二进制补码方式)存储的,占2个或4个字节,见图3.10(b)。
    
    字符变量是用类型符char定义字符变量。
    
    charc=?';
    
    在输出字符变量的值时,可以选择以十进制整数形式输出,或以字符形式输出。
    printf("%d%c
    ",c,c);
    用“%d”格式输出十进制整数63,用%c格式用字符形式输出字符’?’。
    
    如果将一个负整数赋给有符号字符型变量是合法的,但它不代表一个字符,而作为一字节整型变量存储负整数。如:
    signed charc=-6;
    
    #include <stdio.h>//预处理指令
    //全局声明
    void main ( ){
        char c1,c2;
        c1=97;
        c2=98;
        printf("%c %c
    ",c1,c2);
        printf("%d %d
    ",c1,c2);
    }
    
    a b
    97 98
    
    3.2.5浮点型数据…49
    3.2.6怎样确定常量的类型…51
    3.2.7运算符和表达式…52
    

    3.3C语句…57
    3.3.1C语句的作用和分类…57
    ~~~
    字符型数据赋给整型变量时,将字符的ASCII代码赋给整型变量。如:
    i='A';//已定义i为整型变量,由于’A'字符的ASCII代码为65,因此赋值后i的值为65。

    将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量(例如把占4个字节的int 型数据赋给占2个字节的short变量或占1个字节的char变量)时,只将其低字节原封不动地送到被赋值的变量(即发生“截断”)。

    	3.3.2最基本的语句——赋值语句…59
    3.4数据的输入输出…65
    	3.4.1输入输出举例…65
    	3.4.2有关数据输入输出的概念…67
    	~~~
    用尖括号形式(如<stdio.h>)时,编译系统从存放C编译系统的子目录中去找所要包含的文件(如stdio.h),这称为标准方式。如果用双撇号形式(如“stdio.h”),在编译时,编译系统先在用户的当前目录(一般是用户存放源程序文件的子目录)中寻找要包含的文件,若找不到,再按标准方式查找。如果用#include指令是为了使用系统库函数,因而要包含系统提供的相应头文件,以用标准方式为宜,以提高效率。如果用户想包含的头文件不是系统提供的相应头文件,而是用户自己编写的文件(这种文件一般都存放在用户当前目录中),这时应当用双撇号形式,否则会找不到所需的文件。
    如果该头文件不在当前目录中,可以在双撇号中写出文件路径(如#include"C:	emp
    filel.h),以便系统能从中找到所需的文件。
    
    C语言函数库中有一批“标准输入输出函数”,它是以标准的输入输出设备(一般为终端设备)为输入输出对象的。其中有:putchar(输出字符)、getchar(输入字符)、printf(格式输出)、scanf(格式输入)、puts(输出字符串)和gets(输入字符串)。本章主要介绍前面4个最基本的输入输出函数。
    
    3.4.3用printf 函数输出数据…68
    ~~~
    

    c格式符。用来输出一个字符。例如:
    char ch='a';
    printf("%c",ch);

    inta=377;
    printf("%c",a);
    也输出字符y,见图3.18。因为用%c格式输出时,只考虑一个字节,存放a的存储单元中最后一个字节中的信息是01111001,即十进制的121,它是’y'的ASCII代码。

    (3)s格式符。用来输出一个字符串。如:
    printf("%s",CHINA");
    执行此函数时在显示屏上输出字符串"CHINA"(不包括双引号)。

    (2)o格式符。以八进制整数形式输出。将内存单元中的各位的值(0或1)按八进制形式输出,因此输出的数值不带符号,即将符号位也一起作为八进制数的一部分输出。
    例如:
    inta=-1;
    printf("%d %o ",a,a);

    
    

    x格式符。以十六进制数形式输出整数。
    int a=-1;
    printf("%d %o %x ",a,a,a);

    u格式符。用来输出无符号(unsigned)型数据,以十进制整数形式输出。

    	3.4.4用scanf 函数输入数据…75
    	3.4.5字符数据的输入输出…78
    

    想从计算机向显示器输出一个字符,可以调用系统函数库中的putchar函数(字符输出函数)。
    putchar函数的一般形式为
    putchar(c)

    为了向计算机输入一个字符,可以调用系统函数库中的getchar函数(字符输入函数)。
    getchar函数的一般形式为
    getchar()

    
    第5章循环结构程序设计
    	5.1为什么需要循环控制
    	5.2用while语句实现循环
    	5.3用do...while语句实现循环
    	5.4用for 语句5.2用while语
    	5.5循环的嵌套
    	5.6几种循环的比较
    	5.7改变循环执行的状态
    	~~~
    用continue语句提前结束本次循环
    
    continue语句只结束本次循环,而不是终止整个循环的执行。而break语句则是结束整个循环过程,不再判断执行循环的条件是否成立。如果有以下两个循环结构.
    
    5.8循环程序举例
    

    第6章利用数组处理批量数据…142

    一批具有同名的同属性的数据就组成一个数组(array),s就是数组名。
    

    6.1怎样定义和引用一维数组……142
    6.1.1怎样定义一维数组……143
    ~~~
    数组的大小不依赖于程序运行过程中变量的值。如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式。
    void func(int n){
    int a[2*n];
    }

    	6.1.2怎样引用一维数组元素……144
    	6.1.3一维数组的初始化……145
    

    (1)在定义数组时对全部数组元素赋予初值。
    int a[10]={0,1,2,3,4,5,6,7,8,9};

    (2)可以只给数组中的一部分元素赋值。
    int a[10]={0,1,2,3,4};

    (3)如果想使一个数组中全部元素值为0,可以写成
    int a[10]={0};

    (4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组长度。例如:
    int a[5]={1,2,3,4,5);
    可以写成
    int a[]={1,2,3,4,5};

    说明:如果在定义数值型数组时,指定了数组的长度并对之初始化,凡未被“初始化列表”指定初始化的数组元素,系统会自动把它们初始化为0(如果是字符型数组,则初始化为’’,如果是指针型数组,则初始化为NULL,即空指针)。

    	6.1.4一维数组程序举例…146
    6.2怎样定义和引用二维数组…148
    	6.2.1怎样定义二维数组……149
    	~~~
    怎样定义二维数组呢?基本概念与方法和一维数组相似。
    float pay[3][6];
    
    C语言中,二维数组中元素排列的顺序是按行存放的,即在内存中先顺序存放第1行的元素,接着再存放第2行的元素。
    
    6.2.2怎样引用二维数组的元素……150
    6.2.3二维数组的初始化……151
    
    ~~~
    

    (1)分行给二维数组赋初值。例如:
    int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12});

    	6.2.4二维数组程序举例…152
    6.3字符数组……154
    	6.3.1怎样定义字符数组…154
    	~~~
    char c[10];
    
    int c[10]; c[0]=a';
    
    6.3.2字符数组的初始化…155
    ~~~
    

    charc[10]={'',','a','m',','h','a','p','p','y'};

    如果在定义字符数组时不进行初始化,则数组中各元素的值是不可预料的。如果花括号中提供的初值个数(即字符个数)大于数组长度,则出现语法错误。如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的元素自动定为空字符(即')。
    例如:
    char c[10]={'c','','p',r','o','g','r','a','m'};

    	6.3.3怎样引用字符数组中的元素…155
    	~~~
    C系统在用字符数组存储字符串常量时会自动加一个'作为结束符。例如Cprogram"共有9个字符。字符串是存放在一维数组中的,在数组中它占10个字节,最后一个字节’是由系统自动加上的。
    
    6.3.4字符串和字符串结束标志…156
    ~~~
    

    对C语言处理字符串的方法有以上的了解后,再对字符数组初始化的方法补充一种方法,即用字符串常量来使字符数组初始化。例如:
    charc[]={"I am happy"};也可以省略花括号,直接写成
    charc[]="I am happy";
    charc[]={'','','a','m','",'h','a','p','p','y',''};

    printf("%s ",c);/输出数组c中的字符串

    	6.3.5字符数组的输入输出…159
    	~~~
    字符数组的输入输出可以有两种方法。
    (1)逐个字符输入输出。用格式符“%c”输入或输出一个字符,如例6.6。
    (2)将整个字符串一次输入或输出。用“%s”格式符,意思是对字符串(string)的输入输出。例如:
    char c[]={"China");
    printf("%s
    ",c);
    
    (5)可以用scanf函数输入一个字符串。
    scanf("%s",c);
    
    数组中未被赋值的元素的值自动置'。
    char str[13];
    scanf("%s",str);
    
    因为在C语言中数组名代表该数组的起始地址。
    
    分析图6.15所示的字符数组,若数组名为c,占6个字节。数组名c代表地址2000。可以用下面的输出语句得到数组的起始地址。
    printf("%o",c);//用八进制形式输出数组c的起始地址
    
    6.3.6使用字符串处理函数…161
    
    ~~~
    

    puts函数——输出字符串的函数

    char str[]={"China Beijing");
    puts(str);
    在输出时将字符串结束标志'转换成 ’,即输出完字符串后换行。

    gets函数——输入字符串的函数
    gets(str);

    strcat(字符数组1,字符数组2)
    strcat 是STRing CATenate(字符串连接)的缩写。其作用是把两个字符数组中的字符串连接起来,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值一—字符数组1的地址。例如:
    char strl[30]={"People's Republic of");
    char str2[]={"China");
    printf("%s",strcat(str1,str2));
    输出:
    People's Republic of China

    strcpy(字符数组1,字符串2)strcpy是STRingCoPY(字符串复制)的简写。它表示“字符串复制函数”,作用是将字符串2复制到字符数组1中去。例如:
    char strl[10],str2[]="China";
    strcpy(str1,str2);
    连接前两个字符串的后面都有',连接时将字符串1后面的’取消,只在新串最后保留'。

    (2)“字符数组1”必须写成数组名形式(如str1),“字符串2”可以是字符数组名,也可以是一个字符串常量。例如:
    strcpy(str1,"China");作用与前面相同。

    (4)不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组。如下面两行都是不合法的:
    str1="China";/企图用赋值语句将一个字符串常量直接赋给一个字符数组str1=str2;/企图用赋值语句将一个字符数组直接赋给另一个字符数数组

    (5)可以用strncpy函数将字符串2中前面n个字符复制到字符数组1中去。例如:
    strncpy(strl,str2,2);作用是将str2中最前面2个字符复制到str1中,取代str1中原有的最前面2个字符。但复制的字符个数n不应多于str1中原有的字符(不包括')。

    strcmp(字符串1,字符串2)strcmp是STRing CoMPare(字符串比较)的缩写。它的作用是比较字符串1和字符串2。例如:
    strcmp(str1,str2);strcmp("China","Korea");strcmp(str1,"Beijing");说明:字符串比较的规则是:将两个字符串自左至右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到’为止。
    (1)如全部字符相同,则认为两个字符串相等;
    (2)若出现不相同的字符,则以第1对不相同的字符的比较结果为准。

    strlen(字符数组)strlen是STRing LENgth(字符串长度)的缩写。它是测试字符串长度的函数。函数的值为字符串中的实际长度(不包括'在内).
    char str[10]="China";printf("%d',strlen(str));输出结果不是10,也不是6,而是5。也可以直接测试字符串常量的长度,例如:
    strlen("China");

    7.strlwr函数——转换为小写的函数
    其一般形式为
    strlwr(字符串)strlwr是STRing LoWeRcase(字符串小写)的缩写。函数的作用是将字符串中大写字母换成小写字母。

    strupr(字符串)strupr是STRing UPpeRcase(字符串大写)的缩写。函数的作用是将字符串中小写字母换成大写字母。

    include <stdio.h>//预处理指令

    include <string.h>

    	6.3.7字符数组应用举例…165
    7.7数组作为函数参数…192
    	7.7.1数组元素作函数实参…193
    	7.7.2数组名作函数参数……194
    	~~~
    用数组元素作实参时,向形参变量传递的是数组元素的值,而用数组名作函数实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。
    
    因为C语言编译系统并不检查形参数组大小,只是将实参数组的首元素的地址传给形参数组名。因此,形参数组名获得了实参数组的首元素的地址,前已说明,数组名代表数组的首元素的地址,因此,形参数组首元素(array[o])和实参数组首元素(score[0])具有同一地址,它们共占同一存储单元,score[n]和array[n]指的是同一单元。
    
    形参数组可以不指定大小,在定义数组时在数组名后面跟一个空的方括号,如:
    float average(float array[])//定义average函数,形参数组不指定大小
    
    7.7.3多维数组名作函数参数……197
    

    7.8局部变量和全局变量…199
    7.8.1局部变量…199
    ~~~
    定义变量可能有3种情况:
    (1)在函数的开头定义;
    (2)在函数内的复合语句内定义;
    (3)在函数的外部定义。

    	7.8.2全局变量…200
    	~~~
    前已介绍,程序的编译单位是源程序文件,一个源文件可以包含一个或若干个函数。在函数内定义的变量是局部变量,而在函数之外定义的变量称为外部变量,外部变量是全局变量(也称全程变量)。全局变量可以为本文件中其他函数所共用。它的有效范围为从定义变量的位置开始到本源文件结束。
    注意:在函数内定义的变量是局部变量,在函数外定义的变量是全局变量。
    
    为了便于区别全局变量和局部变量,在C程序设计人员中有一个习惯(但非规定),将全局变量名的第1个字母用大写表示。
    

    7.9变量的存储方式和生存期……204
    7.9.1动态存储方式与静态存储方式……204
    ~~~
    还可以从另一个角度,即从变量值存在的时间(即生存期)来观察。有的变量在程序运行的整个过程都是存在的,而有的变量则是在调用其所在的函数时才临时分配存储单元,而在函数调用结束后该存储单元就马上释放了,变量不存在了。也就是说,变量的存储有两种不同的方式:静态存储方式和动态存储方式。静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式,而动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式。

    (1)程序区;
    (2)静态存储区;
    (3)动态存储区。

    数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。

    在动态存储区中存放以下数据:
    ①函数形式参数。在调用函数时给形参分配存储空间。
    ②函数中定义的没有用关键字static 声明的变量,即自动变量
    ③函数调用时的现场保护和返回地址等。

    	7.9.2局部变量的存储类别……205
    	~~~
    2.静态局部变量(static局部变量)有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。
    
    7.9.3全局变量的存储类别……208
    ~~~
    

    一般来说,外部变量是在函数的外部定义的全局变量,它的作用域是从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为程序中各个函数所引用。但有时程序设计人员希望能扩展外部变量的作用域。有以下几种情况:

    1.在一个文件内扩展外部变量的作用域
    如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束。在定义点之前的函数不能引用该外部变量。如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法地使用该外部变量.

    2.将外部变量的作用域扩展到其他文件
    一个C程序可以由一个或多个源程序文件组成。如果程序只由一个源文件组成,使用外部变量的方法前面已经介绍。如果程序由多个源程序文件组成,那么在一个文件中想引用另一个文件中已定义的外部变量,有什么办法呢?

    有的读者可能会问:extern既可以用来扩展外部变量在本文件中的作用域,又可以使外部变量的作用域从一个文件扩展到程序中的其他文件,那么系统怎么区别处理呢?实际上,在编译时遇到extern时,先在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件;如果再找不到,就按出错处理。

    3.将外部变量的作用域限制在本文件中
    有时在程序设计中希望某些外部变量只限于被本文件引用,而不能被其他文件引用。
    这时可以在定义外部变量时加一个static声明。

    用static声明一个变量的作用是:
    (1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
    (2)对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。

    (5)static对局部变量和全局变量的作用不同。对局部变量来说,它使变量由动态存储方式改变为静态存储方式。而对全局变量来说,它使变量局部化(局部于本文件),但仍为静态存储方式。从作用域角度看,凡有static声明的,其作用域都是局限的,或者是局限于本函数内(静态局部变量),或者局限于本文件内(静态外部变量)。

    	7.9.4存储类别小结……212
    7.10关于变量的声明和定义……214
    7.11内部函数和外部函数…215
    	7.11.1内部函数…215
    	~~~
    如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static,即:
    static 类型名函数名(形参表);例如,函数的首行:
    static int fun(int a,int b)
    表示fun是一个内部函数,不能被其他文件调用。
    
    内部函数又称静态函数,因为它是用static声明的。使用内部函数,可以使函数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰。这就使它对外界“屏蔽”了。通常,一个大程序往往分工由不同的人分别编写不同的文件模块,在各人编写自己的文件模块时,不必担心所用函数是否会与其他文件模块中的函数同名。
    通常把只能由本文件使用的函数和外部变量放在文件的开头,前面都冠以static使之局部化,其他文件不能引用。这就提高了程序的可靠性。
    
    7.11.2外部函数…215
    
    如果在定义函数时,在函数首部的最左端加关键字extern,则此函数是外部函数,可供其他文件调用。如函数首部可以为
    extern int fun(int a,int b)
    这样,函数fun就可以为其他文件调用。C语言规定,如果在定义函数时省略extern,则默认为外部函数。本书前面所用的函数都是外部函数。
    
    在需要调用此函数的其他文件中,需要对此函数作声明(不要忘记,即使在本文件中调用一个函数,也要用函数原型进行声明)。在对此函数作声明时,要加关键字extern,表示该函数“是在其他文件中定义的外部函数”。
    

    第8章善于利用指针……220
    8.1指针是什么…220

    一个变量的地址称为该变量的“指针”。例如,地址2000是变量i的指针。如果有一个变量专门用来存放另一变量的地址(即指针),则它称为“指针变量”。
    

    8.2指针变量……222
    8.2.1使用指针变量的例子…222
    8.2.2怎样定义指针变量…223
    ~~~
    在例8.1中已看到怎样定义指针变量,定义指针变量的一般形式为类型名指针变量名;如:
    int
    pointer_1,*pointer_2;

    可以在定义指针变量时,同时对它初始化,如:
    intpointer_1=&a,pointer_2=&b;//定义指针变量pointer_1,pointer_2,并分别指向a,b

    一个变量的指针的含义包括两个方面,一是以存储单元编号表示的地址(如编号为2000的字节),一是它指向的存储单元的数据类型(如int,char,float等)。
    在说明变量类型时不能一般地说“a是一个指针变量”,而应完整地说:“a是指向整型数据的指针变量,b是指向单精度型数据的指针变量,c是指向字符型数据的指针变量”。

    	8.2.3怎样引用指针变量…224
    	~~~
    (1)给指针变量赋值。如:
    p=&a;//把a的地址赋给指针变量p指针变量p的值是变量a的地址,p指向a。
    (2)引用指针变量指向的变量。
    如果已执行“p=&a;”,即指针变量p指向了整型变量a.,则printf("%d',*p);其作用是以整数形式输出指针变量p所指向的变量的值,即变量a的值。
    
    (3)引用指针变量的值。如:
    printf("%o",p);作用是以八进制数形式输出指针变量p的值,如果p指向了a,就是输出了a的地址,即&a。
    
    (1)&取地址运算符。&a是变量a的地址。
    (2)*指针运算符(或称“间接访问”运算符),*p代表指针变量p指向的对象。
    
    8.2.4指针变量作为函数参数…226
    ~~~
    

    函数的参数不仅可以是整型、浮点型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。

    8.3通过指针引用数组……230
    	8.3.1数组元素的指针…230
    	~~~
    在C语言中,数组名(不包括形参数组名,形参数组并不占据实际的内存单元)代表数组中首元素(即序号为0的元素)的地址。因此,下面两个语句等价:
    p=&a[0];//p的值是a[0]的地址
    p=a;//p的值是数组a首元素(即a[o])的地址
    注意:数组名不代表整个数组,只代表数组首元素的地址。上述“p=a;”的作用是“把a数组的首元素的地址赋给指针变量p”,而不是“把数组a各元素的值赋给p”。
    
    在定义指针变量时可以对它初始化,如:
    int*p=&a[0];它等效于下面两行:
    int*p;p=&a[0];//不应写成*p=8&a[o];当然定义时也可以写成
    int*p=a;它的作用是将a数组首元素(即a[o])的地址赋给指针变量p(而不是赋给*p)。
    
    8.3.2在引用数组元素时指针的运算……231
    ~~~
    

    (1)如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,p-1指向同一数组中的上一个元素。注意:执行p+1时并不是将p的值(地址)简单地加1,而是加上一个数组元素所占用的字节数。

    (2)如果p的初值为&a[o],则p+i和a+i就是数组元素a[i]的地址,或者说,它们指向a数组序号为i的元素,见图8.9。
    这里需要注意的是a代表数组首元素的地址,a+1也是地址,它的计算方法同p+1,即它的实际地址为(a+1)×d。例如,p+9和a+9的值是&a[9],它指向a[9],如图8.9所示。

    (3)(p+i)或(a+i)是p+i或a+i所指向的数组元素,即a[i]。例如,(p+5)或(a+5)就是a[5]。即(p+
    5),
    (a+5)和a[5]三者等价。实际上,在编译时,对数组元素a[i]就是按*(a+i)处理的,即按数组首元素的地址加上相对位移量得到要找的元素的地址,然后找出该单元中的内容。

    (4)如果指针变量p1和p2都指向同一数组,如执行p2-p1,结果是p2-p1的值(两个地址之差)除以数组元素的长度。假设,p2指向实型数组元素a[5],p2的值为2020;p1指向a[3],其值为2012,则p2-p1的结果是(2020一2012)/4=2。这个结果是有意义的,表示p2所指的元素与pl所指的元素之间差2个元素。这样,人们就不需要具体地知道p1和p2的值,然后去计算它们的相对位置,而是直接用p2-p1就可知道它们所指元素的相对距离。

    	8.3.3通过指针引用数组元素……233
    	~~~
    根据以上叙述,引用一个数组元素,可以用下面两种方法:
    (1)下标法,如a[i]形式;
    (2)指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值p=a。
    
    (1)从例8.7可以看到,虽然定义数组时指定它包含10个元素,并用指针变量p指向某一数组元素,但是实际上指针变量p可以指向数组以后的存储单元。如果在程序中引用数组元素a[10],虽然并不存在这个元素(最后一个元素是a[9]),但C编译程序并不认此为非法。系统把它按*(a+10)处理,即先找出(a+10)的值(是一个地址),然后找出它指向的单元(*(a+10))的内容。这样做虽然在编译时不出错,但运行结果不是预期的,应避免出现这样的情况。这是程序逻辑上的错误,这种错误比较隐蔽,初学者往往难以发现。在使用指针变量指向数组元素时,应切实保证指向数组中有效的元素。
    
    (2)指向数组的指针变量也可以带下标,如p[i]。有些读者可能想不通,因为只有数组才能带下标,表示数组某一元素。带下标的指针变量是什么含义呢?当指针变量指向数组元素时,指针变量可以带下标。因为在程序编译时,对下标的处理方法是转换为地址的,对p[i]处理成*(p+i),如果p是指向一个整型数组元素a[0],则p[i]代表a[i]。但是必须弄清楚p的当前值是什么?如果当前p指向a[3],则p[2]并不代表a[2],而是a[3+2],即a[5]。建议少用这种容易出错的用法。
    
    8.3.4用数组名作函数参数……237
    
    ~~~
    

    再看用数组名作函数参数的情况。前已介绍,实参数组名代表该数组首元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的。因此,形参应该是一个指针变量(只有指针变量才能存放地址)。实际上,C编译都是将形参数组名作为指针变量来处理的。例如,本小节开头给出的函数fun的形参是写成数组形式的:
    fun(int arr[],int n)
    但在程序编译时是将arr按指针变量处理的,相当于将函数fun的首部写成fun(int*arr,int n)
    以上两种写法是等价的。在该函数被调用时,系统会在fun函数中建立一个指针变量arr,用来存放从主调函数传递过来的实参数组首元素的地址。如果在fun函数中用运算符sizeof测定arr所占的字节数,可以发现sizeof(arr)的值为4(用Visual C++时)或2(用TurboC2.0时)。这就证明了系统是把arr作为指针变量来处理的(指针变量在Visual C++中占4个字节,在Turbo C++中占2个字节)。

    	8.3.5通过指针引用多维数组…245
    	
    	~~~
    从二维数组的角度来看,a代表二维数组首元素的
    地址,现在的首元素不是一个简单的整型元素,而是由4个整型元素所组成的一维数组,因此a代表的是首行(即序号为0的行)的首地址。a+1代表序号为1的行的首地址。如果二维数组的首行的首地址为2000,一个整型数据占4个字节,则a+1的值应该是2000+4×4=2016(因为第0行有4个整型数据)。a+1指向a[1],或者说,a+1的值是a[1]的首地址。a+2代表a[2]的首地址,它的值是2032,见图8.19。
    
    
    a[o],a[1],a[2]既然是一维数组名,而C语言又规定了数组名代表数组首元素地址,因此a[o]代表一维数组a[o]中第0列元素的地址,即&a[o][o]。也就是说,a[1]的值是&a[1][0],a[2]的值是&a[2][0]。
    
    请考虑a数组0行1列元素的地址怎么表示?a[0]是一维数组名,该一维数组中序号为1的元素的地址显然应该用a[0]+1来表示,见图8.20。此时“a[0]+1”中的1代表1个列元素的字节数,即4个字节。a[o]的值是2000,a[0]+1的值是2004(而不是2016)。这是因为现在是在一维数组范围内讨论问题的,正如有一个一维数组x,x+1是其第1个元素x[1]的地址一样。a[0]+o,a[o]+1,a[0]+2,a[0]+3分别是a[0][o],a[o][1],a[o][2],a[o][3]元素的地址(即&a[0][0],&[0][1],&.[0][2],&[0][3])。
    
    前已述及,a[o]和*(a+0)等价,a[1]和*(a+1)等价,a[i]和*(a+i)等价。因此,a[0]+1和*(a+0)+1都是&a[o][1](即图8.20中的2004)。a[1]+2和*(a+1)+2的值都是&a[1][2](即图中的2024)。请注意不要将*(a+1)+2错写成*(a+1+2),后者变成*(a+3)了,相当于a[3]。
    
    进一步分析,欲得到a[o][1]的值,用地址法怎么表示呢?既然a[0]+1和*(a+0)+1是a[o][1]的地址,那么,*(a[0]+1)就是a[o][1]的值。同理,*(*(a+0)+1)或*(*a+1)也是a[o][1]的值。*(a[i]+j)或*(*(a+i)+j)是a[i][i]的值。务请记住*(a+i)和a[i]
    是等价的。
    
    有必要对a[i]的性质作进一步说明。a[i]从形式上看是a数组中序号为i的元素。如果a是一维数组名,则a[i]代表a数组序号为i的元素的存储单元。a[i]是有物理地址的,是占存储单元的。但如果a是二维数组,则a[i]是一维数组名,它只是一个地址,并不代表某一元素的值(如同一维数组名只是一个指针常量一样)。a,a+i,a[],*(a+i),*(a+i)+j,a[i]+j都是地址。而*(a[i]+j)和*(*(a+i)+j)是二维数组元素a[i][j]的值,见表8.2。
    

    首先说明,a+1是二维数组a中序号为1的行的首地址(序号从0起算),而*(a+1)并不是a+1单元的内容(值),因为a+1并不是一个变量的存储单元,也就谈不上它的内容了。*(a+1)就是a[1],而a[1]是一维数组名,所以也是地址,它指向a[1][o]。a[1]和
    *(a+1)都是二维数组中地址的不同表示形式。
    
    再次强调:二维数组名(如a)是指向行的。因此a+1中的“1”代表一行中全部元素所占的字节数(图8.20表示为16个字节)。一维数组名(如a[o],a[1])是指向列元素的。
    a[0]+1中的1代表一个a元素所占的字节数(图8.20表示为4个字节)。在指向行的指针前面加一个*,就转换为指向列的指针。例如,a和a+1是指向行的指针,在它们前面加一个*就是*a和*(a+1),它们就成为指向列的指针,分别指向a数组0行0列的元素和1行0列的元素。反之,在指向列的指针前面加&,就成为指向行的指针。例如a[o]是指向0行0列元素的指针,在它前面加一个&,得&a[0],由于a[0]与*(a+0)等价,因此
    8.a[0]与&*a等价,也就是与a等价,它指向二维数组的0行。
    
    注意:不要把&a[i]简单地理解为a[]元素的物理地址,因为并不存在a[i]这样一个实际的数据存储单元。它只是一种地址的计算方法,能得到第i行的首地址,&a[i]和a[i]
    的值是一样的,但它们的含义是不同的。&a[]或a+i指向行,而a[]或*(a+i)指向列。
    当列下标j为0时,&a[i]和a[i](即a[i]+j)值相等,即它们代表同一地址,但应注意它们所指向的对象是不同的,即指针的基类型是不同的。*(a+i)只是a[i]的另一种表示形式,不要简单地认为*(a+i)是“a+i所指单元中的内客”。在一维数组中a+i所指的是一个数组元素的存储单元,在该单元中有具体值,上述说法是正确的。而对二维数组,a+i不是指向具体存储单元而指向行。在二维数组中,a+i、a[],*(a+i),&a[i],&a[i][o]的值相等,即它们都代表同一地址。请读者仔细琢磨其概念。
    

    ①a[o],a[1],a[2]的类型为int·型(指向整型变量,而a的类型为int(*)[4],指向含4个元素的一维数组.
    
    指向数组元素的指针变量
    
    指向由m个元素组成的一维数组的指针变量
    
    ①inta[4];(a有4个元素,每个元素为整型)
    ②int(*p)[4];第②种形式表示(*p)有4个元素,每个元素为整型。
    第②种形式表示(*p)有4个元素,每个元素为整型。也就是p所指的对象是有4个整型元素的数组,即p是指向一维数组的指针,见图8.24。应该记住,此时p只能指向一个包含4个元素的一维数组,不能指向一维数组中的某一元素。p的值就是该一维数组的起始地址。
    

    8.4通过指针引用字符串…255
    8.4.1字符串的引用方式…255
    ~~~

    include <stdio.h>

    int main(){
    char string[]="I love China!";
    printf("%s ", string);
    return 0;

    include<stdio.h>

    int main(){
    char * string="I love China!";
    printf("%s ", string);
    return 0;

    charstring="I love China!";
    等价于下面两行:
    char
    string;
    string="I love China!";

    可以通过字符指针变量输出它所指向的字符串,如:
    printf("%s ",string);
    %s是输出字符串时所用的格式符,在输出项中给出字符指针变量名string,则系统会输出string所指向的字符串第1个字符,然后自动使string加1,使之指向下一个字符,再输出该字符……如此直到遇到字符串结束标志o'为止。注意,在内存中,字符串的最后被自动加了一个o'(如图8.27所示),因此在输出时能确定输出的字符到何时结束。可以看到用%s可以对一个字符串进行整体的输入输出。

    	8.4.2字符指针作函数参数…259
    	8.4.3使用字符指针变量和字符数组的比较……263
    	~~~
    char*a,str[10];/定义了字符指针变量a和字符数组 str a=str;/使a指向str数组的首元素
    scanf("%s",a);/从键盘输入一个字符串存放到a所指向的一段存储单元中,正确
    

    8.5指向函数的指针…266
    8.5.1什么是函数指针…266
    ~~~
    如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。

    int(p)(int,int);定义p是一个指向函数的指针变量,它可以指向函数的类型为整型且有两个整型参数的函数。p的类型用int()(int,int)表示。

    	8.5.2用函数指针变量调用函数…266
    	8.5.3怎样定义和使用指向函数的指针变量…268
    	8.5.4用指向函数的指针作函数参数…270
    8.6返回指针值的函数……274
    8.7指针数组和多重指针…277
    	8.7.1什么是指针数组……277
    	~~~
    一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。下面定义一个指针数组:
    int*p[4];
    
    8.7.2指向指针数据的指针……280
    8.7.3指针数组作main 函数的形参……282
    

    8.8动态内存分配与指向它的指针变量…285
    8.8.1什么是内存的动态分配……285
    ~~~
    其函数原型为
    void*malloc(unsigned int size);其作用是在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定为无符号整型(不允许为负数)。此函数的值(即“返回值”)是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。如:
    malloc(100);//开辟100字节的临时分配域,函数值为其第1个字节的地址注意指针的基类型为void,即不指向任何类型的数据,只提供一个地址。如果此函数未能成功地执行(例如内存空间不足),则返回空指针(NULL)。

    其函数原型为
    void*calloc(unsigned n,unsigned size);其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
    用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的起始位置的指针;如果分配不成功,返回NULL。如:
    p=calloc(50,4);//开辟50×4个字节的临时分配域,把起始地址赋给指针变量p

    其函数原型为
    void free(void*p);其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc 或malloc函数时得到的函数返回值。如:
    free(p);//释放指针变量p所指向的已分配的动态空间free函数无返回值。

    其函数原型为
    voidrealloc(voidp,unsigned int size);如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用recalloc函数重新分配。
    用realloc函数将p所指向的动态空间的大小改变为size。p的值不变。如果重分配不成功,返回NULL。如
    realloc(p,50);/将p所指向的已分配的动态空间改为50字节

    	8.8.2怎样建立内存的动态分配…285
    	~~~
    以上4个函数的声明在stdlib.h头文件中,在用到这些函数时应当用“#include
    <stdlib.h>”指令把stdlib.h头文件包含到程序文件中。
    
    
    8.8.3void 指针类型……287
    

    8.9有关指针的小结…288

    第9章用户自己建立数据类型…293
    9.1定义和使用结构体变量……293
    9.1.1自己建立结构体类型……293
    9.1.2定义结构体类型变量……295
    9.1.3结构体变量的初始化和引用……297
    9.2使用结构体数组…300
    9.2.1定义结构体数组…300
    9.2.2结构体数组的应用举例…301
    9.3结构体指针…303
    9.3.1指向结构体变量的指针……303
    ~~~
    说明:为了使用方便和直观,C语言允许把(p).num用p一>num来代替,“一>”代表一个箭头,p一>num表示p所指向的结构体变量中的num成员。同样,(p).name等价于p一>name。“一>”称为指向运算符。
    如果p指向一个结构体变量stu,以下3种用法等价:
    ①stu.成员名(如stu.num);
    ②(p).成员名(如(p).num);
    ③p一>成员名(如p->num)。

    	9.3.2指向结构体数组的指针……304
    	9.3.3用结构体变量和结构体变量的指针作函数参数……306
    9.4用指针处理链表…309
    	9.4.1什么是链表…309
    	9.4.2建立简单的静态链表……310
    	9.4.3建立动态链表……311
    	9.4.4输出链表…315
    9.5共用体类型……317
    	9.5.1什么是共用体类型……317
    	9.5.2引用共用体变量的方式……318
    	9.5.3共用体类型数据的特点…319
    9.6使用枚举类型…323
    9.7用typedef 声明新类型名…326
    

    ①先按定义变量的方法写出定义体(如:inti;)。②将变量名换成新类型名(例如:将i换成Count)。
    ③在最前面加typedef(例如:typedef int Count)。
    ④然后可以用新类型名去定义变量。
    简单地说,就是按定义变量的方式,把变量名换上新类型名,并且在最前面加
    “typedef”,就声明了新类型名代表原来的类型。

  • 相关阅读:
    人工智能第一次作业
    在uni-app的textarea中输入纯数字或者英文不换行的问题
    uni-app实现选择图片上传并显示进度条
    减肥计划
    前端时间格式2020-02-11T12:24:18.000+0000转化成正常格式
    男孩和女孩
    java 获取当前年份 月份 日期
    深海收破烂
    如何爱一个人
    随手心情
  • 原文地址:https://www.cnblogs.com/RoyalGuardsTomCat/p/13955841.html
Copyright © 2020-2023  润新知