C语言程序设计(第四版,建议复习用,无死角)

C语言程序设计


第一章 程序设计和C语言

  • 什么是程序?

    程序是指一组计算机能够识别和执行的指令。

  • 计算机语言经历了几个发展阶段?

    机器语言(只能接受0和1组成的指令,计算机直接识别这种语言即机器指令)------>符号语言(汇编语言,即通过汇编程序的软件,将符号语言转换为机器指令;注意(不同型号的计算机的机器语言和汇编语言是不通用的,甲机器用机器语言编写的在乙机器上不能使用),由于机器语言和汇编语言都贴近计算机,所以被称为“低级语言”)-------->高级语言(第一个诞生的是FORTRAN语言)

  • 高级语言经历了哪几个发展阶段?

    非结构化的语言(FORTRAN语言,指编程风格随比较随意,没有严格的规范要求,缺点:程序难以维护的阅读)

    结构化语言(规范程序必须遵循基本结构。缺点:面对规模小的程序,还可以,但是面对较大的程序,就力不从心了)、

    面向对象的语言

    注意:非结构化的语言和结构化语言都是基于过程的语言!这一点要搞清楚。

  • C语言的背景

    C语言的祖先是BCPL语言,起初以BCPL作为基础设计了B语言,但是功能太少,于是结合BCPL和B的优点, 诞生了C语言,目的尽可能加纳各地用它所写的软件对硬件平台的依赖程序,使之具有可移植性。

    最初C语言只是为描述和是按UNIX的操作系统提供的而一种工作语言而设计,到后来,UNIX的90%以上都用C语言改写,随着UNIX普及,C语言很快风靡全世界。

    C语言标准草稿:C89---->C90---->C99

    现在很多软件公司并未实现C99建议的功能,多以C89为基础开发。

  • C语言的编译执行

    • 预处理 【展开头文件/宏替换/去掉注释/条件编译】
    • 编译【检查语法,生成汇编 】 生成.o文件,即obj
    • 汇编【将汇编语言生成对应的二进制数据 】
    • 链接【将目标代码和C 函数的库进行连接生成可执行的二进制机器代码】生成.exe文件
    • 执行【在特定的系统的环境下运行C 语言】
  • 注意一下C语言的可移植性和Java的可移植性的区别

    C语言的可移植性是对比低级语言来说,使得直接编译标准链接库中的大部分功能,不需要修改源代码。

    而Java的可移植性那就牛逼了,它是生成.class文件后,这个class文件无论在什么系统都可以运行。因为使用java .class命令后,它会启动Java虚拟机(JVM),然后JVM会启动类加载器(classLoader),这个类加载器会去硬盘找到我们要执行的.class文件,然后将它装进JVM中,JVM会将.class文件解析成一个二进制,然后操作系统执行二进制和底层硬件平台进行交互。

  • 为什么要return 0呢?

    return 0可以终止一个main函数,并返回值为0,通常 return 0表示正常退出,return 1表示程序异常终止,return -1表示该函数失败,一般用在子函数结尾。return 0还有一个关键作用就是返回程序流程的控制权。

  • 为什么scanf要用&而printf有时候就不需要&呢?

    scanf是写,所以它需要找到我们定义变量的那个地址,而不是值,我们写入的值将会覆盖那个地址里面的值,地址是不会改变的。

    而printf它是读,所以它只需要读那个值即可,不需要取地址符号。如果加上取地址符号,那么输出结果就是变量的地址。

    一般来说,我们定义一条语句时,等号左边就是我们的变量名,它实际上内存的一块地址,而等号右边则是我们要赋给这块地址的值。

  • 预处理指令,如 #include代表的是什么意思?

    就是将stdio.h的文件内容都进来,放在#inlcude里面

  • 说一说.h和.c为后缀的文件区别和特点

    .h文件里面放的是多个.c文件中同名的变量、数组、函数的声明,为了简化操作,只需要将这些内容都放到.h文件中,然后在.c文件中#define ".h"即可。

    .c文件一般存放的是变量、函数、数组的声明。

    联系:.h主要是为了解决文件编译时重复声明的问题。相当于一个接口,可以到处使用。

  • 说一下C程序的步骤

    首先我们输入或编辑一个程序,这个文件要以**.c为后缀**。然后对源程序进行编译,编译的过程分为两个阶段预编译和正式编译,注意这两个阶段是一气呵成的;首先进行编译预处理,例如#include就是将stdio.h头文件的内容都进来,然后通过预处理读进来的信息同程序其他部分组合在一起,组成一个完整的、可以用来进行正式编译的源程序,随后进行正式编译,即对原程序进行检查,判断有没有啥错误。编译完成后我们会得到一个**.obj的程序,注意!这时这个obj还不能够供计算机直接执行,因为一个程序可能包含若干个源程序文件,一次编译只能得到与一个源程序文件相应的目标文件,它只是整个程序的一部分,必须把所有的编译后得到的目标模块全部连接起来,供计算机执行,生成最终的.exe**文件。


第二章 算法-------程序的灵魂

  • 数据结构:对数据的描述。换句话来说,数据结构就是相互之间存在的一种或多种特定关系的数据元素的集合。

  • 程序=算法+数据结构

  • 判断2000-2500年中的每一年是否为闰年,并将结果输出。

    闰年:条件一:能被4整除,而不能被100整除的年份 条件二:能被400整除的年份

     void  test(){
            int i=2000;
            for(i;i<=2500;i++){
                if((i%4==0&&i%100!=0||i%400==0){
                    printf("闰年是:%d\n",i);
                }
            }
    }
    
  • 求1-1/2+1/3-1/4+…+1/99-1/100

    	int sign=-1;
        double  sum=0;
        for ( double i = 1; i <=100; i++)
        {
            sign= -sign;
            sum=sum + sign/i;
        }
        printf("%f\n",sum);
    注意:这里有个易错点,为什么被除数i也要用浮点型?
    总结:
    1.当除数或者被除数之一(只要有一个)是浮点数(或double),进行的就是浮点数除法,会把另一个除数或者被除数转换为精度更高的进行除法(例如int转为floatfloat转为double)结果为浮点数(或double)
    例如float/int 或者int/float,结果都是float
    2.当除数和被除数都是int类型时,即使结果定义为float类型,显示的结果值也是整数
    
  • 给出一个大于或等于3的正整数,判断它是不是一个素数。

    素数:除了1和该数本身以外,不能被其他任何整数整除的数

     int numbertest;//输入数据
        int count=0;//计数
        scanf("%d",&numbertest);
        if (numbertest>=3)
        {	
            for (int j =2; j < numbertest; j++)//核心
            {
                if ((numbertest%j)==0)
                {   
                    count++;
                }
            }	
            if (count==0){
                printf("素数:%d",numbertest);
            }else{
                printf("不是素数:%d",numbertest);
            }
    	} 
     
    
  • 算法的五大特性:

    有穷性、确定性、可行性、输入、输出

  • 课后题

    void test1();//依次将10个数输入,要求输出其中最大的数
    void test2();//有3个数a,b,c 要求按大小顺序把他们输出
    void test3();//将100-200之间的素数输出
    void test4();//求两个数m和n的最大公约数和求最小公倍数
    void test5();//求方程式ax^2+bx+C=0的根。分别考虑:1.有两个不等的实根  2.有两个相等的实根 
    void test1(){
        int number[10];
        int max;
        for (int i = 0; i < 10; i++)//注意一下怎么比用for来完成多个scanf操作就可以---务必涉及数组
        {
            scanf("%d",&number[i]); 
        } 
        max=number[0];
       for (int  i = 0; i < 10; i++)
        {
            if (max<number[i])
            {
                max=number[i];
            }
        }
        printf("max:%d",max);
    }
    void test2();//有3个数a,b,c 要求按大小顺序把他们输出
    void test2(){//从大到小  就是一个排序问题
        int number[3];
        int temp;
        int max;
        for (int i = 0; i < 3; i++)
        {
            scanf("%d",&number[i]);
        }
        for (int j = 0; j < 3; j++)
        {
             max=number[j];
        for (int i = j; i < 3; i++)
        {
            if (max<number[i])
            {
                temp=max;
                max=number[i];
                number[i]=temp;
                
            }
        }
               number[j]=max;
        }
        printf("%d%d%d",number[0],number[1],number[2]);
    }
    void test3();//将100-200之间的素数输出
    void  test3(){
        for (int i = 100; i < 200; i++)
        {
        int count=0;//计数	
            for (int j =2; j < i; j++)//核心
            {
                if ((i%j)==0)
                {   
                    count++;
                } 
        
            }	
            if (count==0){
                printf("%d素数\n",i);
            }else{
                printf("%d不是素数\n",i);
            } 
    		} 
    }
    void test4();//求两个数m和n的最大公约数和求最小公倍数    
    void test4(){//过程要好好思考一下.
        int a,b,temp;
        int max;
        int min;;
        scanf("%d,%d",&a,&b);
        if (a>b)
        {
            temp=a;
            a=b;
            b=temp;
        }
        for (int  i = a; i > 0; i--)
        {
            if ((a%i)==0&&(b%i)==0)
            {
                max=i;
                break;
            }
        }
        printf("最大公约数是%d\n",max);
        min=a*b/max;//数学公式
        printf("最小公倍数为%d\n",min);
    }
    void test5();//求方程式ax^2+bx+C=0的根。分别考虑:1.有两个不等的实根  2.有两个相等的实根 
    void test5(){//注意数据类型取值问题!
        int  a,b,c;
        int disc;
        double x1,x2;
        scanf("%d%d%d",&a,&b,&c);
        disc=b*b-4*a*c;
        if (disc< 0)
        {
            printf("无根");
        }
        else if (disc==0)
        {   
            x1=(-b+sqrt(disc))/(2*a);
            x2=(-b-sqrt(disc))/(2*a);
              printf("有两个相等的根%8.4f,%8.4f2\n",x1,x2);
        }
        else if (disc> 0)
        { 
            x1=(-b+sqrt(disc))/(2*a);
            x2=(-b-sqrt(disc))/(2*a);
            printf("有两个不相等的根%f,%f\n",x1,x2);
        } 
    }
    

第三章 最简单的C程序设计----顺序程序设计

  • 什么是结构化设计?

    采用自顶向下逐步细化的方式 进行设计程序

  • 数据有哪两种表现形式?

    常量(在程序运行时,值不能被修改)和变量(值可以被修改)

    • 常量最常见的有哪几种?

      整型常量:(比如100,200,0,-1)

      实型常量: 包含两种,一种是十进制小数形式,由数字和小数点组成,比如123.1,

      ​ 一种是指数形式,比如12.34e3

      字符常量:包含两种,一种是普通字符,由单撇号包括起来的一个字符,如‘a’,‘b’。不能是‘ab’。

      ​ 一种是转义字符,由\开头的字符序列,比如‘\n’代表换行,‘\t’代表下一个tab

      【注意:字符常量存储在计算机内存中,他并不是存储字符,而是其ASCII代码,比如’A‘表示65,’a‘表示97】

      字符串常量,由双撇号把若干个字符括起来。如“boy”,里面包含的是一个字符串

      符号常量:用#define指令指定用一个符号名代表一个常量。 #define PI 3.12321321

    • 字符串常量和字符常量有什么区别?

      字符串常量和字符常量是不同的量。它们之间主要有以下区别:

      • 字符常量由单引号括起来,字符串常量由双引号括起来。
      • 字符常量只能是单个字符,字符串常量则可以含一个或多个字符。
      • 可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量。在C语言中没有相应的字符串变量。这是与BASIC 语言不同的。但是可以用一个字符数组来存放一个字符串常量。
      • 字符串的存储结构:“Hi Tom cat” -----> Hi Tom cat\0 而字符’H’ -----> H
    • 注意,要区分符号常量和变量。

      符号常量他是不占用内存的,只是一个临时符号,在预编译后这个符号就不存在了,全部置换成了对应的值,故不能对符号常量赋新的值。

      常量就只是一个值,没有啥特殊意义。

    • 变量的定义是什么?(变量就是一个容器)

      一个有名字具有特定属性的一个存储单元,用来存放数据,也就是存放变量的值,

      • 扩展:C99新增了一个常变量,用const来修饰,修饰后这个值不能被改变。
    • 常变量和常量由什么区别?

      常变量 const 具有名字,属性,存储单元,只是不能改变其值,而常量没有名字,就只是一个值。常变量的好处,可以在程序中被引用。

    • 标识符的定义和组成

      标识符就是一个对象的名字。

      组成部分:数字、字母、下划线,首字母不能是数字,因为容易产生二义性。

    • 数据类型

      数据类型分为基本类型、枚举类型(enum)、空类型(void)、派生类型

      • 基本类型包括:整型类型、浮点类型。
        • 整型类型包括:基本整型(int )、短整型(short int)、长整型(long int)、双长整型(long long int)、字符型(char)、布尔型(bool)
        • 浮点类型:单精度浮点型(float)、双精度浮点型(double)、复数浮点型(float_complex,double_complex,long long complex)
      • 派生类型包括:指针类型(*)、数组类型( [ ])、 结构体类型(struct)、共用体类型(union)、函数类型
    • 详细讲解一下整型类型

      编译系统分配给int型数据2个字节或者4个字节(这是由编译器系统自行决定的),比如Turbo C2.0中,它是分配2个字节16位机器上),而Visual C++它是分配4个字节(32位)。补:64位也是4个字节,也有8个的。

    • 整数在计算机存储单元的存储方式是什么?

      计算机是用补码的形式来存放整数的值。

      一个正数的补码是此数的二进制形式,如5的二进制形式是101,如果用两个字节存储一个整数,则形式为00000000 00000101。

      如果是一个负数,则应先求出负数的补码,方法是先将此数的绝对值写成二进制形式,然后对其后面所有二进位按位取反。再加1。

      比如-5的存储形式:
           第一步。取绝对值  ----> 5 ,写出二进制形式
           00000000 00000101
           第二步。按位取反
           11111111 11111010
           第三步。加1
           11111111 11111011
          
      [扩展:]1.最左边一位表示符号,1指负数,0指正数
      	   2.sizeof()是测量类型或变量长度的运算符。
      
    • 整形数据常见的存储空间和值的范围

    • 解释一下什么是无符号类型?

      无符号类型用unsigned来表示,指存储单元中全部的二进制都用来存储数值本身,没有符号位,因此无符号整形变量中可以存放的正数范围比一般整型变量中的正数范围多了一倍

    • unsigned 常见的一个错误点

      unsigned short price=-1;
      printf("%d\n",price);
      问:结果是什么?   -------------------->65535
      这是为什么?
      原因:系统对-1先转换成补码形式,就是全部二进位都是1,然后存入到price中,由于price是无符号短整型变量,其左面第一位不代表符号。
      
    • 详细了解字符型数据

      字符里面什么都可以写吗? ----------->当然不行

      字符只能使用系统的字符集中的字符,目前大多数系统采用ASCII字符集。

      空格字符’ '的ASCII代码是十进制数32。 转义字符‘ \n’的ASCII代码是十进制10

      [小知识:我们知道,字符型变量允许存储的值位-128127,但字符的代码不可能是负值,所以在存储字符时我们只用到了0127这一部分,第一位默认都是0,同样的道理 unsigned char 的范围就成了0~255]

    • 字符型数据常见的存储空间和值的范围
      C语言程序设计(第四版,建议复习用,无死角)_第1张图片

    • 了解浮点型数据

      在C语言中,实数是以指数形式存放在存储单元中。

    • 实型数据常见的存储空间和值的范围

C语言程序设计(第四版,建议复习用,无死角)_第2张图片

【注:long double 在Turbo C中分配的16个字节,而Visual C++分配的是8个字节】

  • 请问,为什么要把常量分为不同的类型呢?

    程序中的常量是要存放在计算机中的存储单元中,这就必须确定分配给他多少字节,按什么方式存储。

  • 一般用来强转限制(了解即可):长整型的表示:long int =213123L; 浮点型表示:float a=3.213123f long double=1.23L

  • 请问 类型与变量的区别是什么呢?

    每一个变量都属于一个确定的类型,类型是变量的一个重要的属性。变量是占用存储单元的,是具体的实体,而类型是变量的共性,是抽象的,它不占用资源,不用来存放数据。

  • 请说一下i++和++i的区别

    I++ 是使用完 I 之后,使得I加1。而++I是使用I之前,使用I加1.

    实验:
    Case1:++i
        int i=3;
    	int j=++i;
    	printf("%d",i);//在这里,i的值会先加1,然后赋值给j
    Case2: i++
        int i=3;
    	int j=i++;
    	printf("%d",j);//在这里,i的值会首先赋给j,然后再加1
    
  • 怎么将一个大写字母转换成小写字母呢?

    char c1,c2;
    c1='A';
    c2=c1+32;
    printf("%c\n",c2);//a
    printf("%d\n",c2);//97
    
  • C语言常见的运算符有那些?

    • 算术运算符 :+ ,-,*,/,%
    • 关系运算符:<,>,!=
    • 逻辑运算符:(!&& | |)
    • 位运算符(<< >> ~ | ^ &)
    • 赋值运算符(=)
    • 条件运算符(?
    • 逗号运算符(,)
    • 指针运算符(* 和 &)
    • 求字节数运算符(sizeof)
    • 强制类型转换运算符((类型))
    • 成员运算符(. ->)
    • 下标运算符([ ] )
    • 其他
  • 常见的控制语句有哪些?

    • if…else…(条件语句)
    • for()…(循环语句)
    • while()…(循环语句)
    • do … while() (循环语句)
    • continue(结束本次循环语句)
    • break(终止执行switch语句或循环语句,注意这个范围!)
    • switch(多分支选择语句)
    • return(从函数返回语句)
    • goto(转向语句,在结构化程序中基本不用goto语句)-
  • a=3 叫表达式, a=3; 加上分号叫表达式语句,只有加上分号才算的上是语句。空语句是指只有一个分号 即 ; 它什么也不做,可以用来作为流程的转向点(流程从程序其他地方转到此语句处。复合语句是指{ float a=1.3; int i=3 ; printf("%d",a);} 即通过一个中括号把一些语句和声明包括起来,也成为语句块

  • 什么是双目运算符?什么是单目运算符?

    双目运算符是指运算符两边都有操作数,单目运算符是指两边只有一个操作数

  • 比较复杂的赋值表达式

    a+=a-=a*a
    怎么分析这个赋值表达式?
    步骤如下:(我们假设这个a初值为12)
    1.先进行a-=a*a的运算,它相当于a=a-a*a,a的值为a=12-12*12,即值为-132
    2.再进行a=a+(-132),a的值为 -132-132=-264
    
  • 注意定义变量以及赋值的规范性

    1.int a,b,c=3;
    它的意义是给c赋值3
    2.int a=b=c=3;
    这也是错误的,没有这种赋值形式。
    
  • C语言中的%7.2f是什么意思?

    1.%7.2f指的是格式化为float的浮点数格式。

    2.其中的7指的是最后输出总的占位符为7位,包含小数点,小数点也占一位,不足时前面补上空格符,缺几个补几个

    3.其中的**.2指的是保留最后的2位小数**。

    4.比如%7.2f的2.5输出就是:(加号代表空格符)+++2.50。

  • 你真的了解输入和输出函数吗?

    C语言本身是不提供输入和输出函数的,输入和输出操作是由C标准函数库中的函数来实现,各种C编译系统提供的系统函数库是由个软件公司编制的,包括了C语言建议的全部标准函数,在使用系统库函数时,要在程序文件的开头用预处理指令#include把有关头文件放在本程序中。一般有两种写法,一种是#include ,另外一种是#include “stdio.h”,作用是一样,但是搜索文件方式不一样,第一种会从存放C编译系统的子目录中去找所要包含的文件,这成为标准方式。而用第二种方式,它会先在用户的当前目录中寻找要包含的文件,若找不到,再按标准方式去找。第二种多用于自己编写的文件寻找。

  • 详细讲解printf函数

    一般格式:printf(格式控制,输出表列)

    格式控制:是用双撇号括起来的一个字符串,称“转换控制字符串”,简称“格式字符串”,它包含两个信息:

    1.格式声明:由“%”和格式字符组成,比如%d、%f等,它的作用是将输出的数据转换成指定的格式然后输出。

    2.普通字符:即需要在输出时原样输出的字符,比如printf里面双撇号内的逗号、空格和换行符。

    输出表列:程序需要输出的一些数据,比如常量、变量、表达式

    一般有那些格式字符呢?

    • d格式符:用来输出一个有符号的十进制整数。拓展:%5d,指的是输出数据占5列。%ld指的是长整型,%lld指的是双长整型

      比如:printf(“%5d\n%5d\n”,12,–345);

      输出结果为: 12(12前面还有3个空格)

      ​ -345 (-345前面有1个空格)

    • c格式符:用来输出一个字符.拓展:%5c也是同上。

    • s格式符:用来输出一个字符串。

      printf(“%s”,“CHINA”);------------------>输出为CHINA

    • f格式符:用来输出实数(包括单、双精度、长双精度),以小数形式输出。拓展:注意,%7.2表示,输出的数据占7列,小数包括2位。

    • e格式符:用格式声明%e指定以指数形式输出实数,注意:如果不指定输出数据所占的宽度和数字部分的小数位数,许多编译器会自动给出数字部分的小数位数为6位数,指数部分占5列。

    • i格式符:作用与d格式符相同,按十进制整形数据的实际长度输出,很少用。

    • o格式符:以八进制整数形式输出。

    • x格式符:以十六进制数形式输出整数。

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

    • g格式符:用来输出浮点数,系统自动选f格式或e格式输出,选择其中长度较短的格式,不输出无意义的0.

  • 详细讲解scanf函数

    一般形式:scanf(格式控制,地址表列)

    格式控制和printf的一样,地址表列是由若干个地址组成的表列,可以是变量的地址,或字符串的首地址

    使用scanf需要注意的问题!!!

    实例:
    scanf("a=%f,b=%f,c=%f",&a,&b,&c);
    正确输入:  a=1,b=2,c=3
    错误输入:	1  3   2   (原因:对应位置不匹配,上面scanf格式中有逗号,而在这用了空格代替,错误!!)
    错误输入2:   a=1 b=2 c=3  (原因,用空格代替逗号,错误!!)
    实例2scanf("%d:%d:%d",&a,&b,&c);
    正确:1:2:3
    实例3:
    scanf("%d%d%d",&a,&b,&c);
    正确:123
    错误:1 2 3(多加空格了)
    实例4scanf("%d%c%f",&a,&b,&c);
    若输入1234a 123o.26,第一个数据对应%d格式,在输入1234之后遇字符’a‘,因此系统认为数值1234后已经没有数字了,第1个数据对应到此结束,把1234送给变量a。把其后面的字符’a‘送给字符变量b,由于%c只要求输入一个字符,系统判定该字符已输入结束,因此输入字符a之后不需要加空格。字符’a‘后面的数值应送给变量c。如果由于疏忽把1230.26错达成123o.26,由于123后面出现字符o,就认为该数值数据到此结束,将123送给变量c,后面几个字符没有被读入。
    总结:我们控制板所输出的内容,要同scanf格式一致,因为它会对应一个一个的扫描。
    
  • 介绍putchar()和getchar()

    putchar():从计算机向显示器输出一个字符,格式为:putchar(c);

    注意:putchar()输出的是字符,而不是输出整数。

    getchar():为了计算机输入一个字符,格式为:getchar();

    注意:使用getchar()时,并不是在键盘上敲一个字符,该字符就立即送到计算机中去的,而是暂存在键盘的缓冲区中,只有摁了Enter键才能把这些字符一起输入到计算机中,然后按先后顺序分别付给相应的变量。

    getchar()不仅可以从输入设备获得一个可显示的字符,而且可以获得在屏幕上无法显示的字符,如控制字符。

  • 整形变量和字符变量是否在任何情况下都可以互相替代

    不是!在有限范围内是可以的,如果超出在字符范围,那就损失精度。


第四章 选择结构程序设计

  • C语言中选择判断语句有哪种?

    if语句 和 switch语句

  • if语句的一般形式有啥些?

    格式1 
    1.if (表达式) 语句1
    2.if (表达式) 语句1    else 语句2
    3.if (表达式) 语句1 	  else if(表达式2) 语句2   else if(表达式3)语句3  .......  else if(表达式m) 语句m 
      else   语句m+1
    
  • if在系统中是怎么运行的?

    不要误以为if语句中 if部分是一个语句;else部分是另外一个语句,不要一看见分号,就以为if语句结束了。在系统对if语句编译时,若发现内嵌语句结束(出现分号),还要检查其后有无else,如果无else,就一位整个if语句结束,结果有else,则把else子句作为if语句的一部分。注意else子句不能作为语句单独使用,它必须是if语句的一部分,与if配对使用。

  • 请你划分一下关系运算符的优先级

    优先级高的一层( < <= > >=)

    优先级低的一层(== !=)

    关系表达式:a>b

  • 请你划分一下算术运算符、关系运算符、赋值运算符、逻辑运算符的优先级

    算术运算符>关系运算符>逻辑运算符>赋值运算符

  • 对于max=(a>b)? a:b 的解释,如果a>b则执行max=a,否则执行max=b;

    试着做一下判断:
    a>b?a:b+1
    分析:相当于a>b? a:(b+1)
    
  • 你真的知道if else 嵌套中的esle吗?

    else总是与它上面的最近的未配对的if配对。所以编程要有好习惯。

  • 详解switch多分支选择结构

    格式: switch(表达式)

    ​ {

    ​ case 常量1 : 语句1

    ​ case 常量2 :语句2

    ​ …

    ​ case 常量n : 语句n

    ​ default : 语句n+1

    ​ }

    ​ 说明:

1.switch 后面括号内的“表达式”,其值的类型应为整数类型(整数类型也含字符类型)

2.可以没有fault标号,此时如果没有与switch匹配的语句,则不执行任何语句。

3.各个case标号出现次序不影响执行结果,例如可以先执行defalut标号,再出现case…

4.每个case常量必须互不相同

5.case标号只起标记作用,在执行switch语句时,根据switch表达式的值找到匹配的入口标号,并不在此进行条件检查。在执行完一个case标号后面的语句后,就从此标号执行下去,不再进行判断。除非有break语句。

6.最后一个case语句(如今的defalut语句)不必加break语句,因为流程已经到了switch结构的结束处。

switch的执行过程:先计算switch的表达式,然后将它与各case标号比较,如果与某一个case标号中的常量相同,流程就转到此case标号后面的语句。如果没有与swtich表达式相匹配的case常量,流程转去执行default标号后面的语句。

  • 课后习题

    void test();
    //**从键盘输入一个小于1000的正数,要求输出它的平方根(如果平方根不是正数,则输出其整数部分)。
    //要求在输入数据后先对其进行检查是否为小于1000的正数,若不是,则重新输入。**
    void test1();//有四个圆塔,圆心分别为(2,2),(-2,2),(2,-2),(-2,-2),高度为10,半径为1,输入坐标判断塔高度
    void test(){
        int num;
        int sqrt;
     
        while (num>=1000)
        {
            scanf("%d",&num);
        }
        
        if (num<1000)
        {
            sqrt=num*num;
            printf("%d\n",sqrt);
        } 
    }
    void test1(){
        int x, y, h;
        double f1, f2, f3, f4;
        //输入坐标
        scanf("%d %d", &x, &y);
        //坐标方程
        f1 = pow(x-2, 2) + pow(y-2, 2);
        f2 = pow(x-2, 2) + pow(y+4<=1)
        {
            h = 10;
        }else{
            h=0;
        }
        printf("This point height is %d\n", h);
    }
    

  • 第五章 循环结构程序设计

  • 解释一下do while 和 while 的区别。

    while的一般形式为:while(表达式) 语句 表达式是一个关系表达式,它的值只能是真或假。

    do…while一般形式为:do 语句 while(表达式); 这个表达式也是一个关系表达式,它的值只能是真或者假。

    两者的区别:从语法上看,do while 后面有个逗号
    从功能上看,do while 里面的语句无论怎么样,都会执行依次,不需要条件判定。

  • 详解for循环

    for的一般形式为:for(表达式1;表达式2;表达式3)

    表达式1:设置初始条件,只执行一次。

    表达式2:是循环条件表达式,用来判定是否继续循环。在每个执行循环体前先执行此表达式,决定是否继续执行循环。

    表达式3:作为循环的调整,例如使循环变量增值,它是在执行完循环体后才开始的。

    那么for循环内部机制到底是怎么执行的呢?

    首先,举一个例子来说明这个过程。
      for (int i = 0; i < 5; i++)
        {
            printf("i的值为:%d\n",i);
        }
    步骤:
    首先for循环会执行表达式1,来进行初始化,然后表达式2进行条件判定,若满足,则运行代码,若不满足,则循环结束.
    下一步,执行表达式3,进行+1操作,然后再进行表达式2判断,若符号,则继续运行代码,否则,循环结束。
    下一步,执行表达式3,进行+1操作,然后再进行表达式2判断,跟上面操作一致。直到不满足条件,跳出循环。
    注意:表达式1,只执行一次。
    

    扩展:for循环的三个表达式都是可以省略的,但是需要提前赋值,否则没有什么太大意义;另外,省略归省略,但是不能掉去分号。

  • 讲完了for循环,那能说一下foreach循环吗?

    注意,foreach循环是Java里面的for循环的增强版。主要用于遍历集合。

    格式: for(数据类型 自定义输出值:集合对象){ 执行语句 }

    举个简单的例子来看一下

    List<Integer> intList = new ArrayList<Integer>();
    for (Integer i: intList) {
        System.out.print(i);
    }
    
  • 拓展一下for循环的效率问题。

    下面来讲解三种循环方式,你比较一下速度。
    
    One:第一种for循环:(int i = 0; i < arrayInt.length; i++)(内置判断)
    /*
    		 * 第一种for循环,循环体中使用 i < arrayInt.length作为判断语句
    		 * */
    		for(int i = 0; i < arrayInt.length; i++){
    			System.out.print(1);
    		}
    		
    Two:第二种for循环:(int len = arrayInt.length;)(外置判断)
    
    /*
    		 * 第二种for循环,数组大小提前用变量存储
    		 * */
    		int len = arrayInt.length;
    		for(int i = 0;i < len; i++){
    			System.out.print(2);
    		}
    Three:第三种for循环增强for循环:(foreach循环)(增强for循环)
    
    /*
    		 * 增强for循环,foreach循环
    		 * */
    		for (int i : arrayInt) {
    			System.out.print(3);
    		}
    		
    结果:
    第一种for循环(内置):14ms
    第二种for循环(外置):6ms
    第三种for循环(foreahch):3ms
    
    
  • 说一下break和continue的区别?

    使用范围:首先 break 主要用在switch多分支判断以及循环语句当中,而continue主要用于循环语句当中

    break的语法格式:break; continue的语法格式:continue;

    区别:break主要用于跳出循环体,执行循环体外下面的语句

    ​ continue主要用于 跳出本次循环,继续执行下一次循环操作。

  • for循环求Π值

在这里插入图片描述

分析:首先,题目要求我们求Π(pai),我们要知道,Π在机器中是不被识别的。题目给出条件,直到发现某一项的绝对值小于10^-6为止,我们可以把它当作我们的循环判定条件。设它为item,另外提醒,绝对值的符号是在另一个库中,所以我们要加载这个库。
void test1();//测试pai/4 约等于 1-1/3+1/5-1/7+......直到发现某一项的绝对值小于10^-6为止
void test1(){
   double item=1.0;
   double num=0.0;
   double pai;
   double addnum=1.0;
   int sign;//这用来转换正负号
   
   while(fabs(item)>=1e-6)//题目要求最小一项不能小于10的负6次方
   {
       num=num+item;
       sign=-sign;
       addnum=addnum+2;
       item=sign/addnum;
   }
    pai=num*4;
    printf("%f\n",pai);
   
}

  • 利用for循环求解Fibonacci数列的前40个数。

    void test2();//测试求解Fibonacci数列的前40个数,第一个第二个数必须为1.比如 1  1  2  3  5  8  13  21  后面的数等于前面两个数之和
    void test2(){
      int a,b,c;
      a=1;
      b=1;//B是第二位
      printf("c:%d\n",a);
      printf("c:%d\n",b);
      for (int i = 0; i < 38; i++)
      {
          c=a+b;
          a=b;
          b=c;//
          printf("c:%d\n",c);
      }
    }
    
  • 判定一个大于3的整数n,是否为素数(也成为质数)

    void test3();//素数是只能够被1和自身整除的数。
    前面已经讲过,这里用书上的方法实现一下
    void test3(){
    int n,i;
    printf("please enter a integer number, n=?");
    scanf("%d",&n);
    for(i=2;i<=n-1;i++)
    	if(n%i==0) break;
    	if(i<n) printf("%d is not a prime number.\n",n);
    	else printf("%d is a prime number.\n",n);
    	return 0;
    }
    
    改进:其实不必被2~(n-1)范围内的各整数去除,只需要将n被2~n/2间的整数除即可,或者被2~根号n之间的整数除即可。
    void test3Improve(){
    	int n,i,k;
    	printf("please enter a integer number, n=?");
    	scanf("%d",&n);
    	k=sqrt(n);
    	for(i=2;i<=k;i++)
    	if(n%i==0) break;
    	if(i<n) printf("%d is not a prime number.\n",n);
    	else printf("%d is a prime number.\n",n);
    	return 0;
    }
    
  • 尝试将字符串“China!”转换称“Glmre!”

    分析:我们发现字符串中的对应字符,都相差4个,由此出发,设计一下程序。
    void test4(){
      char c;
      c=getchar();
      while (c!='\n')
      {
        if (c>='a'&&c<='z'||c>='A'&&c<='Z')
        {
          if (c>='W'&&c<='Z'||c>='w'&&c<='z')
          {
            c=c-22;
          }
          else{
            c=c+4;
          }
         printf("%c\n",c);
         c=getchar();//继续读取缓冲区字符
        }
     }
    
  • 课后习题:

    void test();//输入一行字符,分别统计出其中英文字母、空格、数字和其它啊字符的个数
    void test1();//Sn=a+aa+aaa+aaaa..........
    void test2();//1!+2!+3!+4!+5!+......20!
    void test3();//求k的1~100次和 + 求k^2的1~50次和 + 求1/k的1~10次和
    void test4();//水仙花:一个三位数,水仙花是是指其各位数字立方和等于该数本身。
    void test5();//完数:一个数恰好等于它的因子之和,例如6的因子为1,2,3,而6=1+2+3,因此6是完数,找出1000内的所有完数  
    void test6();//有一个分数序列 2/1,3/2,5/3,8/5,13/8,21/13....,求前20项之和
    void test7();//猴子吃桃问题。猴子第一天摘下若干个桃子,当即吃了一半,吃的不过瘾,又多吃了一个,第二天,吃了剩下的一半,
                    //又多吃了一个,等到第十天想吃,就剩下一个桃子了,问第一天摘了多少桃子
    void test8();//用二分法求下面方程在(-10,10)的根:2x^3- 4x^2+3x-6=0 
    void test9();//输出菱形
    void test10();//两个乒乓球队进行比赛,各出3人。甲队为A,B,C 3人,乙队为X,Y,Z 3人。已抽签决定比赛名单。
    //有人向队员打听比赛的名单,A说他不和X比,C说他不和X,Z比,请编程序找出3对赛手的名单。
    
    void test();//输入一行字符,分别统计出其中英文字母、空格、数字和其它啊字符的个数
    void test(){
        char c;
        int word=0;
        int kongge=0;
        int number=0;
        int others=0;
        c=getchar();
        while (c!='\n')//\n是回车符
        {
            if (c>='a'&&c<='z'||c>='A'&&c<='Z')
            {
                word++;
            }
            else if (c>='0'&&c<='9')//注意。这里是字符,不是整数。因为是从char里面提取出来的。
            {
                number++;
            }
            else if (c==' ')
            {
               kongge++;
            }
            else{
                others++;
            }
         c=getchar();
        }
        printf("数字个数为:%d\n",number);
        printf("英文个数为:%d\n",word);
        printf("空格个数为:%d\n",kongge);
        printf("其他个数为:%d\n",others);
        
    }
    void test1();//Sn=a+aa+aaa+aaaa..........
    void test1(){
        int a=2;
        int n;
        int sum=0;
        scanf("%d23",&n);
        for (int i = 0; i < n; i++)
        {
            sum=sum+a;
            a=a*10+2;
        }
        printf("%d\n",sum);
        
    }
    void test2();//1!+2!+3!+4!+5!+......20!
    void test2(){
        int total=0;
        int sum;
        int j=1;
        for (j; j <=20; j++)
        {   
             sum=1;
            for (int  i = 1; i <=j; i++)
            {   
                sum=sum*i;
            }
            total=total+sum;
        }
        printf("total:%d",total);
    }
    void test3();//求k的1~100次和 + 求k^2的1~50次和 + 求1/k的1~10次和
    void  test3(){
        int k;
        int sum1=0;
        int sum2=0;
        int sum3=0;
        int total=0;
        scanf("%d",&k);
        for (int i = 1; i <=100; i++)
        {
            sum1=sum1+k;
        }
        for (int i = 1; i <= 50; i++)
        {
            sum2=sum2+k*k;
        }
        for (int i = 1; i <=10; i++)
        {
            sum3=sum3+1/k;
        }
        total=sum1+sum2+sum3;
        printf("total:%d",total);
    }
    void test4();//水仙花:一个三位数,水仙花是是指其各位数字立方和等于该数本身。
    void test4(){
        int number;
        int temp=0;
        int sum=0;
        scanf("%d",&number);
        int gewei=0;
        int shiwei=0;
        int baiwei=0;
        baiwei=number/100;
        printf("baiwei:%d\n",baiwei);
        temp=number%100;
        if (temp>10)
        {
            shiwei=temp/10;
                printf("shiwei:%d\n",shiwei);
            gewei=temp%10;
            printf("gewei:%d\n",gewei);
        }else{
            shiwei=0;
            printf("shiwei:%d\n",shiwei);
            gewei=number%100;
            printf("gewei:%d\n",gewei);
        }
        sum=(baiwei*baiwei*baiwei)+(shiwei*shiwei*shiwei)+(gewei*gewei*gewei);
        
        if (sum==number)
        {
           printf("我是水仙花数");
        }else{
            printf("我不是");
        }
    }
    void test5();//完数:一个数恰好等于它的因子之和,例如6的因子为1,2,3,而6=1+2+3,因此6是完数,找出1000内的所有完数 
    void test5(){ //重点练
        //分析:怎么求因子?
    int data, fator, sum;      /* data表示要判断的数,fator表示因子,sum表示因子之和*/
    
    	for (data = 2; data <= 1000; data++)
    	{
    		//1是所有整数的因子,所以因子之和从1开始
    		sum = 1;
    		for (fator = 2; fator <= data / 2; fator++)
    		{
    			/* 判断data能否被fator整除,能的话fator即为因子  因子不包括自身 */
    			if (data % fator == 0)
    			{
    				sum += fator;
    			}
    		}
    		// 判断此数是否等于因子之和 */
    		if (sum == data)    
    		{
    			printf("%d its factors are 1, ", data);
    			for (fator = 2; fator <= data / 2; fator++)
    			{
    				if (data % fator == 0)
    				{
    					printf("%d, ", fator);
    				}
    			}
    			printf("\n");
    		}
    	}
    
    }
    void test6();//有一个分数序列 2/1,3/2,5/3,8/5,13/8,21/13....,求前20项之和
    void test6(){//无非就是Fibonacci数列的变化
        double a,b,c;
        double d;
        a=1.0;
        b=1.0;
        double sum=0;
        for (int i = 0; i < 20; i++)
        {
            c=a+b;
            a=b;
            b=c;
            d=c/a;
            sum=sum+d;
        }
        printf("%f\n",sum);
    }
    void test7(){
        int sum=0;
        int now=1;
        //分析:我们从后往前推,即前一天的桃子数量=(后一天的桃子数量+1)✖2
        for (int i = 0; i < 9; i++)//已经吃了9天桃子了
        {   
      
            sum=(now+1)*2;
            now =sum;
        }
        printf("sum:%d\n",sum);
        
    }
    //用二分法求下面方程在(-10,10)的根:2x^3- 4x^2+3x-6=0 
    void  test8(){
        double left = -10, right = 10, mid;
    	double temp = 10;
    	while (fabs(temp) > 1e-5)
    	{
    		mid = (left + right) / 2;
    		//((2x - 4)*x + 3) * x - 6 ==> 2x^3 - 4x^2 + 3x -6
    		temp = ((2 * mid - 4) * mid + 3) * mid - 6;
    		if (temp > 0)
    		{
    			right = mid;
    		}
    		else if (temp < 0)
    		{
    			left = mid;
    		}
    	}
    	printf("在(-10,10)的根为:%lf", mid);
    }
    void test9(){
        /*分析:首先我们要分析行、空格、星号之间的关系,最终得出,前4行: 空格数 = 3 - 行号 ;星星数 = 2 * 行号 + 1
                后三行中:空格数 = 行号 + 1;行号与星星的关系:星星数 = 7 - 2 * (行号+1)
        */
    	int cur_row, space_count, start_count;
    	//输出前4行内容
    	for (cur_row = 0; cur_row < 4; cur_row++)
    	{
    		//计算当前行空格数量,并且进行打印
    		for (space_count = 3 - cur_row; space_count > 0; space_count--)
    		{
    			printf(" ");
    		}
    		//计算当前行*数量,并且进行打印
    		for (start_count = 2 * cur_row + 1; start_count > 0; start_count--)
    		{
    			printf("*");
    		}
    		printf("\n") ;
    	}
    	//输出后三行
    	for (cur_row = 0; cur_row < 3; cur_row++)
    	{
    		for (space_count = cur_row + 1; space_count > 0; space_count--)
    		{
    			printf(" ");
    		}
    
    		for (start_count = 7 - 2 * (cur_row + 1); start_count > 0; start_count--)
    		{
    			printf("*");
    		}
    		printf("\n");
    	}
    }
    
        void test10(){//没做出来
        int A_battle, B_battle, C_battle;
    	//如果A对战的对象从“X”到“Z”
    	for (A_battle = 'X'; A_battle <= 'Z'; A_battle++)
    	{
    		//如果B对战的对象从“X”到“Z”
    		for (B_battle = 'X'; B_battle <= 'Z'; B_battle++)
    		{
    			//如果C对战的对象从“X”到“Z”
    			for (C_battle = 'X'; C_battle <= 'Z'; C_battle++)
    			{
    				//去除限制条件
    				if (A_battle == 'X' || C_battle == 'X' || C_battle == 'Z' || B_battle == A_battle || B_battle == C_battle || A_battle == C_battle)
    				{
    					continue;
    				}
    				printf("A对%c,B对%c,C对%c", A_battle, B_battle, C_battle);
    			}
    		}
    	}
       }
    

    第六章 利用数组处理批量数据

  • 数组的定义和特性:

    - 一批具有同名的同属性的数据就组成了一个数组

    - 数组是一组有序数据的集合。数组中的各数据的排列是有一定规律的。

    - 数组中的每一个元素都属于同一个数据类型

  • 怎么定义一维数组?

    一般形式:类型符 数组名【常量表达式】;

  • 数组的注意事项有哪些?

    • 定义数组时,我们要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度。我们要注意:a[4]指的是a[0],a[1],a[2],a[3],指的是这4个值,没有a[4]。

    • c语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值

      比如:int n,scanf(”%d”,&n); int a[n];

    • 如果在被调用的函数(不包括主函数)中定义数组,其长度可以是变量或非常量表达式

      比如:void func(int n){int a[2*n];}这是合法的,在调用func函数时,形参n从实参得到值。这种情况叫做“可变长数组”。

  • 一维数组的初始化方式有哪些?

     在定义数组时对全部数组元素赋予初值。比如
      int a[10]={0,1,2,3,4,5,6,7,8,9};
      
    可以给数组中的一部分元素赋值。例如:
      int a[10]={0,1,2,3,4};
    
     如果想使一个数组中全部元素值为0,可以写成
      int a[10]={0};
    
     在对数组元素赋值的时候,可以不指定数组长度,
      Int a[]={1,2,3,4,5};
    
     如果数组长度和赋值个数不一样,会自动初始化为0
      int a[10]={1,2,3,4,5}; 前面5个元素被赋值,后面5 个元素被初始化为0.如果是字符型数组,那后面的讲会被赋值为’\0‘,如果是指针型数组,则初始化为null,即空指针
    
  • 一维数组的应用

    排序的经典算法必须会

  • 怎么定义一个二维数组?

    二维数组常称为矩阵。

    一般格式:数组名【常量表达式】【常量表达式】

    例如:float a[3][4],b[5][10];
    
    不能写成 float a[3,4],b[5,10];
    
  • 二维数组的存在方式?

    逻辑上是矩阵,但是在内存中各元素是连续存放的,不是二维的,是线性的。

    注意:b[2][3]=a[2][3]/2;这样赋值也是可以的
    
  • 二维数组的初始化?

    int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
    解释:第一个花括号的数据给第一行元素,第二个花括号的给第二行。
    或
    int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};这种方法是按照内存中的排列顺序对各元素赋值。
    
  • 二维数组对指定元素赋值

    int a[3][4]={{1},{5},{9}}; 只是对各行第一列的元素赋初始值,其余元素自动为0.
    

    注意:二维数组不能出现a【】【】这种情况,行列必须至少有一个。

  • 二维数组的经典练习

    void test();//如何实现矩阵的转置
    
    void test(){
        int a[2][3]={{1,2,3},{4,5,6}};
        int b[3][2];
        printf("转置之前\n");
        for (int i = 0; i < 2; i++)
        {
            for (int  j = 0; j < 3; j++)
            {   
                printf("%d",a[i][j]);
                printf(" ");
                b[j][i]=a[i][j];
            }
            printf("\n");  
        }
        printf("转置之后\n");
        for (int  i = 0; i < 3; i++)
        {
            for (int j = 0; j < 2; j++)
            {
                printf("%d",b[i][j]);
                printf(" ");
            }
            printf("\n");
            
        } 
    }
    
  • 字符数组注意c语言是没有字符串类型的,字符串是存放在字符型数组中的

    字符数组的赋值:

    char c [10];
    c[0]=‘i’;c[1]=‘ ’;c[2]=‘a’;c[3]=‘m’………………
    或
    char c[10]={’i’,’ ’,’a’,’m’…….};
    如果花括号的值多于定义的数组长度,那么报错,若长度大于花括号的值,则后面自动赋值空字符。
    也可以
    char c[]={’i’,’ ’,’a’,’m’……..};char c[]=“i am not happy”;
    
  • 什么是字符串结束标志?

    在实际工作中,人们关心的往往是字符串的有效长度而不是字符数组的长度。例如:定义一个字符数组长度诶100,实际有效字符只有40个。为了测量字符串的实际长度,c语言规定了一个”字符串结束标志”,以字符‘\0’作为结束标志。如果字符数组中存有若干字符,前面9个字符都不是空字符(’\0’),而第10个字符是’\0’,则认为数组中有一个字符串,其有效字符为9个,即在遇到字符’\0’时,表示字符串结束,把它前面的字符组成一个字符串。

    c系统在用字符数组存储数字串常量时,会自动加一个‘\0’作为结束符。若某个字符串占9个字符,那么在数组中它就占10个字符,最后一个字节’\0’是由系统自动加上的。

  • 字符数组的输入输出

    逐个字符输入输出:用格式符“%c”输入或输出一个字符。

    将整个字符串一次输入或输出:用“%s”格式符,意思是对字符串的输入和输出.

    举例子:char c[]=“china”; printf(”%s\n”,c);

    输出时,遇结束符’\0’就停止输出。结果为china

    注意:1.输出的字符串中不包含结束符’\0’。

    ​ 2.用“%s”格式输出字符串时,printf函数中的输出项是字符数组名,而不是数组元素名,写成下面这样是不对的。printf(“%s”,c[0]);

    ​ 3.如果一个字符数组中包含一个以上‘\0’,则遇第一个‘\0’时输出就结束了。

  • 解释一下为什么scanf后面变量如果是数组,为什么不用加&呢?

    比如:char c[6]; scanf(”%s”,c);

    我们从键盘输入china,系统会自动在china后面加一个’\0’结束符。

    比如:char str1[5],str2[5],str3[5];scanf(”%s%s%s”,str1,str2,str3);

    如果利用一个scanf函数输入多个字符串,则应在输入时以空格分隔。

    输入:how are you?

    由于有空格字符间隔,作为3个字符串输入。在输入完后,str1,str2和str3数组的状态如下:

    how\0\0are\0\0you?\0

    了解这些,我们开始回归正题。

    scanf函数中的输入项如果是字符数组名,不要再加地址符&,因为在c语言中,数组名代表该数组的起始地址。通常我们输出字符数组名c,系统会找到其数组起始地址,然后逐个输出其中的字符,知道遇 ‘\0’为止。

  • 常见的字符串处理函数有哪些?

    • puts函数:输出字符串的函数 gets函数:输入字符串的函数

      一般形式:puts(字符数组) 作用:将一个字符串(以’\0’结束的字符序列)输出到终端,用的不是很多。

      一般形式:gets(字符数组) 作用:从终端输入一个字符串到字符数组,并得到一个函数值。该函数只是字符数组的起始地址。

      注意:puts和gets 都能输出或输入一个字符串,不能写成puts(str1,str2)或gets(str1,str2)。

    • strcat 字符串连接函数

      一般形式:strcat(字符串1,字符串2) 作用:把两个字符数组的字符连接起来,把字符串2连接到字符串1上,并把结果放到字符串1。

      注意:字符数组1必须足够大,以便容纳连接后的新字符串。 连接前两个字符串的后面都有’\0’,连接时将字符串1后面的‘\0’取消,只在新串最后保留‘\0’。

    • strcpy和strncpy函数 字符串复制函数

      一般形式**:strcpy(字符数组1,字符串2)** 作用:将字符串2复制到字符数组1中去。

      注意:字符数组1 必须是数组名的形式,例如strcpy(str,“China”);

      为什么要有这个字符串复制函数?-----因为系统是不允许用赋值语句将一个字符串常量或字符数组直接给一个字符数组。

      比如:str1=“China”; str1=str2;

      一般形式:strncpy(字符数组1,字符串2,个数) 作用:将字符串2中的前面n个字符复制到字符数组1中前n个字符中去

      注意:它是将字符串2 中的最前面2个字符复制到数组1中,取代数组1 中最前面的2个字符。不要搞错。

    • strcmp函数 字符串比较函数

      一般形式:strcmp(字符串1,字符串2) 作用:比较字符串1和字符串2

      比较原则:将两个字符串自左至右逐个字符相比(按ASCII码),直到出现不同的字符为止或者遇到‘\0’为止。

      如果字符串1=字符串2 ,则函数值为0

      如果字符串1>字符串2,则函数值为一个正整数

      如果字符串1<字符串2,则函数值为一个负整数。

    • strlen函数 测字符串长度的函数

      一般形式:strlen(字符数组) 作用:测量字符串长度的函数,不包括’\0’。

    • strlwr函数 转换为小写的函数

      一般形式:strlew(字符串) 作用: 将字符串的大写字母转换成小写字母

    • strupr函数 转换为大写的函数

      一般形式:strupr(字符串) 作用:将字符串的小写字符转换称大写字母

  • 怎么设计一个程序,来判定一句话有多少个单词?

    char string[81];
    int i,num=0,word=0;
    char c;
    gets(string);
    for(i=0;(c=string[i]!='\0';i++)
        if(c==' ') //空格的ASCLL是32
        	word=0;
        else if(word==0){word=1;num++;}
    printf("There are %d words in this line.\n",num);
    
  • 测试题

    void test1();//杨辉三角形,
    1
    1   1
    1   2   1
    1   3   3   1
    1   4   6   4   1
    void test2();//魔方阵  :每一行、每一列、对角线之和都相等。
    解:魔方阵中各数的排列规律如下:
    (1)1放在第1行的中间一列。
    (2)2开始直到n×n止各数依次按下列规则存放:每一个数存放的行比前一个数的行数减1,列数加1(例如上面的三阶魔方阵,54的上一行后一列)。
    (3)如果上一数的行数为1,则下一个数的行数为n(指最下一行)。例如,1在第一行,则2应放在最下一行,列数同样加1.
    (4)当上一个数的列数为n时,下一个数的列数应为1,行数减1.例如,2在第3行最后一列,则3应放在第2行第1列。
    (5)如果按上面规则确定的位置上已有数,或上一个数是第一行第n列时,则把下一个数放在上一个数的下面。例如,按上面的规定,4应该放在第1行第2列,但该位置已经被1占据,所以4就放在3的下面。由于6是第1行第3列(即最后一列),故7放在6下面。
    按此方法可以得到任何阶的魔方阵。
          8 1 6
          3 5 7
          4 9 2
    void test3();//判断鞍点,给定一个n*n矩阵A。矩阵A的鞍点是一个位置(i,j),在该位置上的元素是第i行上的最大数,第j列上的最小数。一个矩阵A也可能没有鞍点。
    void test4();//有一篇文章,共有3行文字,每行有80个字符。要求分别统计出其中英文大写字母,小写字母,数字,空格以及其他字符的个数。
    void test5();//有一段电报,第一个字母变成第26个字母,第i个字母变成第(26-i+1)个字母  方法二:提前声明两个字母表,去找对应位置。
    /*
    *****
     *****
      *****
       *****
        *****
    */
    void test1(){
        int a[10][10]={{1,0}};
        for (int i = 0; i < 10; i++)
        {
            for (int j = 0; j<= i; j++)
            {
                if (i==j){//对角线全为1
                    a[i][j]=1;
                }
                else if (j==0){//第一列全为1
                    a[i][j]=1;
                }else{//规律:中间等于正上方元素加斜上方元素。按照三角计算,即所求元素,假如求a22,那a22=a12+a11,除了对角元素和第一列都是这个规律,知道这个就迎刃而解了。
                    a[i][j]=a[i-1][j]+a[i-1][j-1];
                }
                printf("%d",a[i][j]);
                printf(" ");
            }
            printf("\n");
            
        } 
    }
    void test2(){
        int a[15][15],i,j,k,p,n;
      p=1;
      while(p==1)
        {printf("enter n(1--15):");    //要求阶数为1~15之间的奇数
         scanf("%d",&n);
         if((n!=0)&&(n<=15)&&(n%2!=0))
          p=0;
        }
    //初始化
      for(i=1;i<=n;i++)
        for(j=1;j<=n;j++)
          a[i][j]=0;
    //建立魔方阵   
      j=n/2+1;
      a[1][j]=1;
      for(k=2;k<=n*n;k++)
      {i=i-1;
       j=j+1;
       if((i<1)&&(j>n))
       {i=i+2;
        j=j-1;
      }
       else
         {if(i<1)i=n;
          if(j>n)j=1;
         }
       if(a[i][j]==0)
         a[i][j]=k;
       else
         {i=i+2;
          j=j-1;
          a[i][j]=k;
         }
       }
          //输出魔方阵
      for(i=1;i<=n;i++)
        {for(j=1;j<=n;j++)
            printf("%5d",a[i][j]);
          printf("\n");
        }
    }
    void test3(){
        int maxh=0,minl=0,tempmax=0,tempmin=0;
        int a[3][3]={{8,1,6},{3,5,7},{4,9,2}};
        maxh=a[0][0];
        minl=a[0][0];
        for (int i = 0; i < 3; i++)
        {    
            for ( int j = 0; j < 3; j++)
            {   
                if (maxh<a[i][j])
                {                       
                    maxh=a[i][j];
                    tempmax=j;              
                }
               
            }
            if (minl>a[i][tempmax]){
                minl=a[i][tempmax];
                tempmin=i;
             }
        }
             if (maxh==minl){
                 printf("有鞍点%d,%d",tempmax,tempmin);
            }else{
                printf("没有鞍点");
            }
    }
    
    void test4(){
        char word[3][80];
        int i,j,a,b,c,d,e;
        a=b=c=d=e=0;
        printf("输入3行文字:\n");
        for(i=0;i<3;i++)
        {
            gets(word[i]);
            for(j=0;j<80&&word[i][j]!='\0';j++)
            {
                if(word[i][j]>='A'&&word[i][j]<='Z') a++;
                else if(word[i][j]>='a'&&word[i][j]<='z') b++;
                else if(word[i][j]>='0'&&word[i][j]<='9') c++;
                else if(word[i][j]==' ') d++;
                else e++;   
            }
        }
        printf("大写字母%d个\n小写字母%d个\n数字%d个\n空格%d个\n其他字符%d个\n",a,b,c,d,e);
    }
    void test5(){
        char str[]={"*****"};
            int i,k;
        for(i=1;i<=5;i++)
        {
            printf("%s\n",str);
            for(k=1;k<=i;k++) 
            {
                char c=' ';
                printf("%c",c);
            }   
        }
    }
    void test6(){
    
        char a[10],b[10];
        int i;
        printf("输入一行电文:\n");
        gets(a);
        for(i=0;a[i]!='\0';i++)
        {
            if('A'<=a[i]&&a[i]<='Z') b[i]=155-a[i];//'A'+'Z'=155 
            else if('a'<=a[i]&&a[i]<='z') b[i]=219-a[i];//'a'+'z'=219
            else b[i]=a[i];
        }
        b[i]=0;
        printf("原密码为:\n");
        puts(a);
        printf("密码翻译成原文为:\n");
        puts(b);
    }
    

    第七章 用函数实现模块化程序设计

    • 什么是模块化程序设计?

      采用组装的办法来简化程序设计的过程

    • 什么是函数

      函数就是功能。void函数是函数无类型,执行这个函数不会把任何值带回main函数

      注意:
      1.一个源程序文件是一个编译单位,在程序编译时是以源程序文件为单位进行编译的,而不是以函数为单位进行编译的。 C程序的执行是从main函数开始的,在main中调用其他函数,在调用完成后返回到main函数。
      2.所有函数都是平行的,定义函数是分别进行的,是相互独立的。函数是不可以嵌套的。函数间可以互相调用,但是不能 调用main函数,因为main函数是被操作系统 调用的
      3.函数分为两种,有参函数和无参函数

    • 怎么样定义函数?

      先定义,后使用。 对于C编译系统提供的库函数,是编译系统事先定义好的,里面包括了对函数的定义,程序这不必自己定义,只须用#include指令把有关文件的包括到本文件模块中即可。

      定义方法: 类型名 函数名(形式参数列表){ 函数体 } 或 类型名 函数名(void) {函数体} 这种带void的,表示空,即函数没有参数

    • 怎么调用函数?

      一般形式:函数名(实参列表) 实参列表可有可没有,但是括号是不能省略的!

      注意:调用函数并不一定要求包括分号,只要作为函数调用语句才需要分号。如果作为函数表达式或函数参数,函数调用本身是不必有分号的。

    • 什么是形参?什么是实参?

      形参是指在定义函数时函数后面括号中的变量名称为形参,或者成为虚拟参数。

      实参是指在主函数中调用一个函数时,括号里面的参数成为实际参数。这个实际参数可以是常量,变量,表达式。

    • 你能说一下函数调用过程中参数的内存变化吗?

      在定义函数中指定的形参,在没有出现函数调用之前,他们是不占内存的,在发生函数调用时,函数max的形参被临时分配内存单元。会把我们输入的实参传递给形参,然后进行有关运算,最后调用结束,形参单元被释放。

      注意:实参向形参的数据传递是”值传递“,单向传递,只能由实参传向形参,而不能由形参传给实参。在内存中,形参和实参占有不同的存储单元。

    • 为什么要提前声明函数?

      之所以要提前声明函数,是为了让系统记下函数的有关信息,根据声明的而函数来对我们编译的函数进行全面检查,如果有错误,他们出现出错信息,利于改正,而不事先声明函数,那么系统无法确定函数是规范,一旦在运行时发现错误,修改是比较麻烦的。

      系统只关心参数个数和参数类型,而不检查参数名。参数名写不写都可以。

      形式:函数类型 函数名(参数类型1 参数名1 ,参数类型2 参数名2 ,…)

      ​ 或 函数类型 函数名(参数类型1 ,参数类型2 ,…,参数类型n)

    • 区分声明和定义

      定义是指对函数功能的确立,包括指定函数名、函数类型、形参以及其类型以及函数体,它是一个完整的、独立的函数单体。

      而函数的声明,则是把函数名字、函数类型以及形参类型及其个数告诉编译系统,以便在调用该函数时系统按此进行对照检查。

    • 函数可以嵌套调用吗?

      函数是可以嵌套调用的,但是不可以嵌套声明。

    • 什么是递归调用?

      递归调用就是出现直接或间接地调用该函数本身,成为函数地递归调用,但是注意,递归调用必须要有结束条件,否则就是一个无限循环。

    • 递归经典算法

      int test(int n);//递归调用n的阶乘  
      int test(int n){
          int  f;
          if (n>0)
          {
          f=n*test(n-1);
          }else{
              f=1;
          } 
          return f;
      }
      int main(){
          int n;
          int result;
          scanf("%d",&n); 
          result=test(n);
          printf("restlt:%d",result);
          return 0;
      }
      
    • Hanoii汉诺塔问题

      void hanoi(int n,char x,char y,char z){      
          /* 
      我们将n-1个盘子当作一个整体:这就是类似于分治求解子问题的思想
      那么,前一步也就是f(n - 1, other variables)显然是先将n -1 个在A柱子上的盘子通过C柱移动到B柱上,
      再将在A柱子上的编号为n的盘子移动到C柱上,再将B柱子上的n-1个盘子通过A柱移动到C柱上
          */
        	if(n==0)  return;	
          else{
              hanoi(n-1,x,z,y);
              printf("%c---->%c\n",x,z);
              hanoi(n-1,y,x,z);
          } 
      }
      
    • 数组作为函数参数

      • 数组名可以用作实参和形参
      • 数组元素可以做函数实参,但不能做形参。因为形参是在函数被调用时临时分配存储空间的,不可能为一个数组元素单独分配存储单元(因为数组是一个整体,在内存中占连续的一段存储单元)。
      • 注意:在进行scanf地址输入时,如果是地址名,那本身是个地址,所以不需要加&,但是,如果是array[1]那就需要加了,因为它本身是个值】
      • 用数组元素做实参时,向形参变量传递的是数组元素的值,而用数组名做函数实参时,向形参传递的是数组首元素的地址。
      • 形参数组可以不指定大小,在后面跟一个空的[ ]即可,比如 void verge(float array[]);但是二维数组不可以,至少指定一个。
    • 说一下为什么用数组名作函数实参时,可以访问任意数据?

      这里要注意,我们用数组名做函数实参时,不是把数组元素的值传递给形参,而是把实参数组的首元素的地址传递给形参数组,这样两个数组就共占同一段内存单元

    • 说一下局部变量和全局变量的区别(作用域)。

      局部变量是指在一个函数内部定义的变量只在本函数范围内有效

      全局变量是指在函数之外定义的变量,全局变量可以为本文件中其他函数所共用。有效范围:从定义变量的位置开始到本源文件结束。(全局变量的声明有一个特点,就是变量名首字符大写。)

      注意:
      1.主函数定义的变量也只在主函数中有效,并不因为在主函数中定义而在整个文件或程序中有效,主函数也不能使用其他函数中定义的变量。

      ​ 2.不同函数中可以使用同名的变量,他们代表不同的对象,互不干扰,因为在函数结束后,局部变量值会释放。

    • 全局变量有什么缺点?

      全局变量在程序的全部执行过程中都占用存储单元,而不是仅在需要时才开辟单元。

      使函数的通用性减低了,如果在函数内引用了全局变量,那么执行情况会受到有关的外部变量的影响。

      使用全局变量过多,会降低程序的清晰性

    • 如果局部变量和全局变量同名,全局变量在局部变量内会被屏蔽掉。

    • 变量分为哪两种生存期?

      • 静态存储方式:指在程序运行期间由系统分配固定的存储空间的方式
      • 动态存储方式:在程序运行期间根据需要进行动态地分配存储空间的方式
    • 存储空间分为哪三部分?

      1.程序区 2.静态存储区 3.动态存储器区

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

    • C的存储类别包括哪4种?

      • 自动地(auto):系统会给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。大部分局部变量都是auto类型,系统自动补充。

      • 静态的(static):使得局部变量的值在函数调用后不消失而继续保留原值。它属于静态存储类别,注意,静态局部变量在函数调用后仍然存在,但是在其他函数是不能引用它的。

      • 寄存器的(register):如果有一些使用频繁的变量,为提高效率,允许将局部变量的值放到CPU中的寄存器中,不需要访问内存,寄存器的存取速度远远高于内存的存取速度。

      • 外部的(extern):用处1:如果在最后面结果处定义一个变量,那么我们想要在开头使用这个变量,需要加extren+变量名修饰

        ​ 用处2:一个文件定义属性,可以在另一个文件中用extern来获取。

      【注意:如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0或空字符’\0’。而对自动变量来说,它的值是一个不确定的值。】

    • 什么是变量的声明和定义?

      声明:用于向程序表明变量的类型和名字 ,不需要建立存储空间 定义:用于为变量分配存储空间,还可为变量指定初始值。

    • 什么是内部函数和外部函数?

      内部函数是指一个函数只能被本文件中其他函数所调用,它成为内部函数,前面需要加static。

      外部函数是指一个函数可以被其他文件调用,需要加extern

    • 练习

      void test();//输入一个16进制,输出相应的十进制   将十六进制的(2B)H转换为十进制的步骤如下:
      // 1. 第0位 B x 16^0 = 11;
      // 2. 第1位 2 x 16^1 = 32;
      // 3. 读数,把结果值相加,11+32=43,即(2B)H=(43)D。
      void convert();//用递归方法将一个整数n转换为字符串 字符串数组=数字+48   ASCII转换
      void test(){
            char strHex[10];  
            int i = 0, len = 0, temp = 0;  
            long long answer = 0;  
            gets(strHex);  
            len = strlen(strHex);  
          for (i = 0; strHex[i] != '\0'; ++i){  
               switch(strHex[i]){  
                  case 'A': temp = 10; break;  
                  case 'B': temp = 11; break;  
                  case 'C': temp = 12; break;  
                  case 'D': temp = 13; break;  
                  case 'E': temp = 14; break;  
                  case 'F': temp = 15; break;  
                 default: temp = strHex[i]-'0'; break;  
              }  
               answer += (temp*pow(16,len-1-i));  
           }  
           printf("%lld",answer);  
      }
      
      void convert(int n){
      	int i;
      	if ((i = n / 10) != 0)
      		convert(i);
      	putchar(n % 10 + '0');//注意 '0'的ASCILL码是48
      }
      
      int main(){
          //test();
          int num;
      	scanf("%d", &num);
      	if (num < 0) {
      		printf("-");
      		num = -num;
      	}
      	convert(num);
      	printf("\n");
      }   
      

第八章 善于利用指针(8-10章的练习题精选后续补上,不从事C完全没必要熟练使用指针,会看即可)

  • 数据在内存中是怎么存储的?

    在程序进行编译时,系统会给这个变量分配内存单元。编译在根据程序中定义的变量类型,分配一定长度的空间。比如整型变量分配4个字节,内存区的每一个字节有一个编号,这就是地址,在地址所标志的内存单元中存放的数据则相当于旅游房间中居住的旅客。

    我们将地址形象化的成为指针,意思是通过它能找到以它为地址的内存单元

    实际上,我们通常访问变量,是通过变量名找到存储单元的地址,从而对存储单元进行存取操作的。程序经过编译以后已经将变量名转换为变量的地址,对变量值的存取都是通过地址来进行的。这种直接按变量名进行的访问,成为直接访问方式。

  • 什么是指针,什么是指针变量?(好好理解这句话)

    指针是一种保存变量地址的变量

    一个变量专门用来存放另一变量的地址,则称为指针变量指针变量的值是地址

    注解:别把指针想的那么难,指针就是一个变量,只不过它是指针类型的变量,功能是存放地址的而已。

  • 怎么定义指针变量?

    类型名 * 指针变量名;

    扩展:指针变量是基本数据类型派生出来的类型,他不能离开基本类型而独立存在。

    注意事项:1.指针变量前面的 * 表示该变量的类型为指针型变量。

    ​ 2.在定义指针变量时必须指定数据类型,即基类型。为什么呢?因为想通过指针访问一个变量,只知道其地址是不够的, 无法判断是从地址为2000的一个字节中取出一个字符数据,还是从20001中取出short数据,还是…

    ​ 3.一个变量的指针的含义包括两个方面:一是以存储单元编号表示的地址,一个是它指向的存储单元的数据类型。

    ​ 4.指针变量中只能存放地址,不要将一个整数赋予给一个指针变量。

  • 引用指针变量常用的两个符号是什么?

    &:取地址符 *:指针运算符,即指针所指的对象值

  • 指针变量作为函数参数

    作用:将一个变量的地址传送到另一个函数中。

    #include
    //利用指针实现两个整数的大小顺序输出
    void swap(int *a,int *b){
        int *temp;
        temp=a;
        a=b;
        b=temp;
        printf("n:%dm:%d\n",*a,*b);
    }
    int main(){
        int n,m;
        scanf("%d,%d",&n,&m);
        int *p,*q;
        p=&n;
        q=&m;
        if (n<m)
        {
            swap(p,q);
        }else{
            printf("n:%dm:%d\n",n,m);
        }
        
    }
    
  • 通过指针引用数组

    每个数组元素都在内存中占用存储单元,它们都有相应的地址,指针变量即可以指向变量,也可以指向数组元素。

    通常用指针引用数组有两种方式:

    //方式一
    int a[10]={1,2,3};
    int *p;
    p=&a[0];
    //方式二(常用)
    int a[10]={1,2,3};
    int *p;
    p=a;
    
  • 在引用数组元素时指针的运算

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

    通常有两种方法:下标法(a[i]) 指针法(* (a+i)或 *(p+i)),其中a是数组名,p是指向数组元素的指针变量

    #include
    //指针指向数组的三种用法
    void test();
    void test1();
    void test2();
    void test(){
        int a[10]={1,2,3,4,5,6,7,8,9,10};
        for (int  i = 0; i < 10; i++)
        {
            printf("%d ",a[i]);
        } 
    }
    void test1(){
        int a[10]={1,2,3,4,5,6,7,8,9,10};
        for (int  i = 0; i < 10; i++)
        {
            printf("%d ",*(a+i));
        } 
    }
    void test2(){
        int *p;
        int a[10]={1,2,3,4,5,6,7,8,9,10};
         for (p=a; p <(a+10); p++)
        {
            printf("%d ",*p);
        } 
    }
    int main(){
        test();
        printf("\n");
        test1();
         printf("\n");
        test2();
    }
    
    三种方法比较:第三种方法比前两种都快,用指针变量直接指向元素,不必每次都必须重新计算地址。
    
  • 用数组名做函数参数

    比如fun(int arr[],int n) 等价于  fun(int *arr,int n)
     
        void fun(arr[],int n){
        	printf("%d\n",*arr);//输出array[0]
        	arr=arr+3;//形参数组名可以被赋值
        	printf("%d\n",*arr);//输出array[3]
    }
    
  • 通过指针引用多维数组

    a       						代表二维数组名,指向一维数组a[0],0行首地址
    a[0],*(a+0),*a					00列元素地址
    a+1,&a[1]						1行首地址
    a[1],*(a+1)						10列元素,a[1][0]的地址
    a[1]+2,*(a+1)+2,&a[1][2]		 12列元素 , a[1][2]的地址
    *(a[1]+2),*(*(a+1)+2),a[1][2]	  12列元素 ,a[1][2]的值
    

    为了方便记忆,举一个例子。在军训中,一个排分3个班,每个班战成一行,3个班为3行,相当于一个二维数组,为了方便,班和战士的序号也从0开始。请考虑班长点名和排长点名有什么区别?班长点名,从第0个展示开始逐个检查,班长的移动是横向的,而排长的移动是纵向的。排长看来只走了一步,但是实际上却跳过了一个班10个展示。相当于从a移动到了a+1;而班长则是 a[i]+j 。二维数组a相当于排长,而每一行(a[0],a[1],a[2])相当于班长,每一行中的元素a[1][【2 】相当于战士。

  • 指向由m个元素组成的一维数组的指针变量

    int main(){
        int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,33};
        int (*p)[4],i,j;
        p=a;
        printf("please input row and colum:")scanf("%d,%d",&i,&j);
        printf("a[%d,%d]=%d\n",i,j,*(*(p+i)+j));
    }
    
    输出结果:
    --->please input row and colum:1,2
        a[1,2]=13
    分析:
    注意 int (*p)[4] 这种格式,它代表的是p为一个指针,指向包含4个整形元素的一维地址,即通过p指针,可以看成已经将数组转成
    每行4个字母的格式,长度是16个字节(一行4个元素,一个元素4个字节)。p指针指向a[0],p+1指向a[1],p+2指向a[2].
        
    在实参与形参如果是指针类型,应当注意他们的类型必须一致,不应该把int * 型的指针传给int (*)4】型的指针变量,反之亦然。
    
  • 通过指针引用字符串

    方法一:
    int main(){
        char string[]="I love China";
        pirntf("%s\n",string);
        return 0;
    }
    方法二:
    int main(){
    	char * string="I love China";
    	printf("%s\n",string);
    	return 0;
    }
    注意:这个string 是指向字符串的第一个字符,不是指向字符串。系统输出string所指向的第一个字符,然后自动使string加1,使之指向下一个字符,再输出该字符........直到遇到字符串结束标志'\0'为止。
    
    应用:
    //将字符string赋值到copy中。采用字符串指针
    int main(){
        //方法一:
        // char string[]="I Love China";
        // char copy[12];
        // int i=0;
        // for (i ; *(string+i)!='\0'; i++)
        // {
        //    *(copy+i)= *(string+i);
        // }
        // *(copy+i)='\0';
        // printf("%s",copy);
        
        //方法二
        char a[]="I Love China";
        char copy[12];
        char *p1,*p2;
        p1=a;
        p2=copy;
        for (; *p1!='\0'; p1++,p2++)
        {
            *p2=*p1;
        }
        *p2='\0';
        printf("string b is:%s\n",copy);  
    }
    
  • 使用字符指针和字符数组的区别是什么?

    • 字符数组由若干个元素组成,每个元素中放一个字符;而字符指针变量中存放的是地址,即第一个字符的地址。

    • 可以对字符指针变量赋值,但是不可以对数组名赋值

      char *a;
      a="I love China";//这是OK的,但是赋给a的不是字符串地址,而是字符串的第一个元素的地址
      
      char str[14];
      str[0]='I';//合法
      str="I love China";//不合法,数组名是地址,是常量 , 不能被赋值
      
    • 初始化的区别

      char *a="I love China";
      等价于
      char *a;
      a="I love China!";
      
      而对于数组来说,
      char str[]="I love China";
      不等价于
      char str[14];
      str[]="I love China";//不合法,企图把字符串赋值给数组各元素
      
      
    • 从存储单元来看,编译时为字符数组分配若干各存储单元,以存放各元素的值,而对字符指针变量,只分配一个存储单元。

    • 指针变量的值是可以改变的,而数组名代表一个固定的值,不能改变。

      char * a="I love CHina";
      a=a+7;
      printf("%s\n",a);
      ----->CHina
      
    • 字符数组中个元素的值是可以改变的,但字符指针变量指向的字符串常量中的内容是不可以被取代的。

      char a[]="House";
      char *b="House";
      a[2]='r';//合法
      b[2]='r';//不合法
      
      
  • 什么是函数指针?

    如果在程序中定义一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段空间的起始地址称为这个函数的指针

  • 怎么定义函数指针?

    一般形式:*类型名 (指针变量名)(函数参数表列);

    注意事项:

    1.定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型函数;比如int (*p)(int,int);表示指针变量p只能指向函数返回值为整型且由两个整数参数的函数。在一个程序中,一个指针变量可以先后指向同类型的不同函数。

    2.如果要用指针调用函数,必须先使指针变量指向该函数 p=max,注意不需要给参数,如果给了参数,那就等于赋值了,我们想要的结果是赋予地址,不是值!

    3,在调用函数指针时,只需将(* p)代替函数名即可,在(* p)之后的括号中根据需要写上实参、比如c=(* p)(a,b);

    4.对指向函数的指针变量不能进行算数运算,比如p+n,p++,p–等运算是无意义的。

    5.用函数名调用函数,只调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。

  • 用指向函数的指针做函数参数

    指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数。

    举例子

    int fun(int x,int y,int (* p)(int ,int));
    int max(int,int)int min(int ,int);
    int add(int ,int );
    int main(){
    	int a=34,b=-21,n;
        printf("please choose 1,2 or 3:");
        scanf("%d",&n);
        if(n==1) fun(a,b,max);
        else if(n==2) fun(a,b,min);
        else if(n==3) fun(a,b,add);
        return 0;
    }
    
    int fun(int x,int y,int (*p)(int,int)){
        int result;
        result=(* p)(x,y);
        printf("%d\n",result);
    }
    
    int max(int x,int y){
     //省略
    }
    int min(int x,int y){
     //省略
    }
    int add(int x,int y){
     //省略
    }
    
    本列可以看出,不论是调用max,min或add,函数fun都没有改变,只是改变实参函数名而已。增加了函数使用的灵活性、
    
  • 指针数组和多重指针

    一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。

    一般形式:*类型名 数组名[数组长度]

    指针数组比较适合用来指向若干个字符串,使字符串处理更加方便灵活。例如,图书馆有若干本书,想把书名放在一个数组中,然后对其进行排序各查询,也就是说name[0]中存放”Follow me“的字符地址,name[1]中存放"BASIC"的首字符的地址,如果想要对字符串排序,不必改动字符串位置,只需要改动指针数组中各元素的指向(即个字符的首地址)即可。

    举例子:

    将若干字符串按字母顺序输出
    void sort(char *name[],int n);
    void print(char *name[],int n);
    int main(){
        char * name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};//指针数组
        int n=5;
        sort(name,n);
        print(name,n);
        return 0;
    }
    void sort(char *name[],int n){
        char *temp;
        int i,j,k;
        for(i=0;i<n-1;i++){
            k=i;
            for(j=i+1;j<n;j++)
                if(strcmp(name[k],name[j])>0) k=j;
            if(k!=i){
                temp=name[i];
                name[i]=name[k];
                name[k]=temp;
            }
        }
        
        void print(char *name[],int n){
            int i;
            for(i=0;i<n;i++){
                printf("%s\n",name[i]);
            }
        }  
        
        
    }
    
  • 指向指针数据的指针

    简称指向指针的指针,定义 char * * 指针名;

    情形一:
    int main(){
        char * name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};//指针数组
        char **p;
        int i;
        for(i=0;i<5;i++){
            p=name+i;
            printf("%s\n",*p);
        }
        return 0;
    }
    
    -->Follow me
      BASIC
      Great Wall
      FORTRAN
      Computer design
      
    情形二:
    int main(){
        int a[5]={1,3,5,7,9};
        int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
        int **p,i;
        p=num;
        for(i=0;i<5;i++){
            printf("%d",**p);
            p++;
        }
        printf("\n");
        return 0;
    }
    
  • 指针数组作main函数的形参

    格式: int main(int argc, char * argv[])

    argc,argv是程序的命令行参数,argc是参数个数,argv是参数向量,它是一个指针数组,数组中每一个元素指向命令行中的一个字符串

    通常main函数和其他函数组成一个文件模块,有一个文件名。对文件进行编译和连接,得到可执行文件。用户执行这个可执行文件,操作系统就调用main函数,然后由main函数调用其他函数,从而完成程序的功能。

  • 什么时候main函数需要参数?main函数的形参是从哪里传递给他们的呢?

    main函数的形参值不可能在程序中得到,是由操作系统调用的,实参只能由操作系统给出。在操作命令状态下,实参是和执行文件的命令一起给出的、例如在DOS\LINUX等系统的操作命令状态下,在命令行中包括了命令名和需要传给main函数的参数。

    命令行的一般形式:

    命令名 参数1 参数2 … 参数n

  • 动态分配内存与指向它的指针变量 头文件

    全局变量是分配在内存中的静态存储区,非静态的局部变量是分配在内存中的动态存储区,这个存储区是一个称为栈的区域

    此外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要随时开辟,不需要随时释放,这种数据存储在一个特别的自由存储区,称为堆区。可以根据需要,像系统申请所需大小的空间,由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用

    一般形式:

    **void * malloc(unsigned int size); **

    作用是在内存的动态存储区中分配一个长度为size的连续空间。形参size的类型定为无符号整型(不允许为负数)。此函数的值是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。

    calloc函数 一般形式:void * calloc(unsigned n,unsigned size);

    作用是内存的动态存储去中分配n各长度为size的连续空间,这个空间一般比较大,足以保存一个数组。

    free函数 一般形式:void free(void * p);

    作用是释放指针变量p所指向的动态控件,使这部分空间能被其他变量使用。p影视最近换一次调用malloc或calloc函数所得到的函数返回值。

    *realloc函数 一般形式为 void * realloc (void p,unsigned int size);

    如果已经通过malloc和calloc函数获得了动态空间,想改变其大小,可以用realloc函数重新分配。用realloc函数将p所指向的动态空间大小转变为size,p的值不变,如果重分配不成功,返回NULL

    上述四种类型,都是void类型,称为无类型指针,即不指向哪一种具体的类型数据,指标是用来指向一个抽象的类型的数据,即仅提供一个纯地址,而不能指向任何具体的对象,其类型可以强转。

    注意:不要把"指向void类型"理解为能指向任何的类型的数据,而应该理解为指向空类型或者不指向确定的类型的数据,在将它的值赋给另一指针变量时由系统对他进行类型转换,使之适合于被赋值的变量的类型。

总结:

int i --------------------->定义整型变量i

int * p--------------------->定义p为指向整形数据的指针变量

int a[5]----------------------->定义整型数组a,有5个元素

int *p[4]------------------------>定义指针数组p,它由4个指向整形数据的指针元素组成

int (* p)[4] ---------------------->p为指向包含4个元素的一维数组的指针变量

int f() ------------------------------>f为返回整形函数值的函数

int *p() ----------------------------->p为返回一个指针的函数,该指针指向整形数据

int (* p)() -------------------------------> p为指向函数的指针,该函数返回一个整型值

int **p-------------------------------->p是一个指针变量,它指向一个指向整型数据的指针变量

void * p ----------------------------->p是一个指针变量,基类型为void,不指向具体的对象。


第九章 用户自己建立数据类型

  • 什么是结构体?

    允许用户自己建立由不同类型数据组成的组合型的数据结构

    一般形式:struct 结构体名 {成员列表};

    注意:只建立了一个结构体类型,它相当于一个模型,并没有定义变量,系统不会对他分配存储单元,相当于设计好了图纸,但并未建成具体的房屋。为了能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据。可以采用一下3种方法定义结构体类型的变量。

    1.先声明结构体类型,再定义该类型的变量。

    struct Student{
    char sex;
    int  age;
    };
    Struct Student  students1,students2;
    结构体类型名		结构体变量名(对于上面两个)
    

    2.在声明类型的同时定义变量

    struct Student{
    char sex;
    int  age;
    } student1,student2;
    

    3.不指定类型名而直接定义结构体类型变量

    struct{
    成员变量
    }变量名表列;
    

    注意:结构体变量中的成员,可以单独使用,它的作用与地位相当于普通变量,但是如果程序中有变量与其相同,两者不代表同一对象。

  • 结构体的初始化和引用

    #include
    struct Student{
        long int num;
        char sex[10];
        char name[20];
    }student1={10100,"女","艾菲"};
    int main(){
        printf("NI.:%ld\nname:%s\nsex:%s\n",student1.num,student1.name,student1.sex);
        return 0;
    }
    
    //方法二  这种方法只能在C99标准上使用
    struct Student{
        long int num;
        char sex[10];
        char name[20];
    };
    int main(){
        struct Student student1={.name="艾菲",.num=123,.sex="女"};
    
         printf("NI.:%ld\nname:%s\nsex:%s\n",student1.num,student1.name,student1.sex);
    }
    
    注意:1.引用结构体变量中成员的值,一般使用结构体变量名.成员名
    	 2.不能通过输出结构体变量名来输出结构体变量所有成员的值,Java有toString 是可以的。
    	 3.对结构体里面的成员可以像普通变量一样进行各种运算。
    	 4.同类的结构体变量可以进行互相赋值
    	 5.可以通过scanf("%d",&student.num)这个来给结构体赋值。
    
    
  • 结构体数组

    结构体数组格式1struct 结构体名{
    	成员表列
    }数组名[数组长度];
    结构体格式2:
    结构体类型 数组名[数组长度]   比如struct Person leader[3];
    
    拿投票候选人来举例子
    #include
    #include
    #include
    struct Person
    {
        char name[20];
        int count;
    }leader[3]={"Li",0,"Zhang",0,"Sun",0};
    int main(){
        int i,j;
        char leader_name[20];
        for ( i = 0; i <=10; i++)
        {
            scanf("%s",leader_name);
            for (j  = 0; j < 3; j++)
            {
                if (strcmp(leader_name,leader[j].name)==0) leader[j].count++;         
            }
             printf("\nResult:\n");
             for (int i = 0; i < 3; i++)
             {
                printf("%5s:%d\n",leader[i].name,leader[i].count);
             }       
        } 
    }
    
  • 结构体指针

    指向结构体对象的指针变量既可以指向结构体变量,也可以指向结构体数组中的元素,指针变量的基类型必须与结构体变量的类型相同。例如:struct Student *pt;//pt可以指向struct Student类型的变量或数组元素。

    #include
    #include
    struct Student{
        long  num;
        char name[20];
        char sex;
        float score;   
    };
    int main(){
        struct Student student1;
        struct Student *yuyi;
        yuyi=&student1;
        strcpy(student1.name,"yuyi");
        student1.sex='n';
        student1.num=21;
        student1.score=331.0;
        printf("%d\n%f\n%s\n%c\n",student1.num,student1.score,student1.name,student1.sex);
        printf("%d\n%f\n%s\n%c\n",(*yuyi).num,(*yuyi).score,yuyi->name,yuyi->sex);
    }
    
    为了使用方便和只管,C语言允许把(*p).num用p->num来代替,两者是等价的。
    另外要注意利用strcpy来赋值字符串这种操作方式
    
  • 指向结构体数组的指针

    #include
    #include
    struct Student{
        long  num;
        char name[20];
        char sex;
        float score;   
    }student1[3]={{10101,"Li LIn",'M',18},{10102,"Ai D",'M',19},{10103," Deep Learning",'V',21}};
    int main(){
      struct Student *yuyi;
        for (yuyi= student1; yuyi < student1+3; yuyi++)
        {
         printf("%d\n%f\n%s\n%c\n",yuyi->num,yuyi->score,yuyi->name,yuyi->sex);
        }
    }
    
  • 区分(++p)->num 和 (p++)->num的区别

    (++p)->num:先使得p加1,然后得到p指向的元素中的num成员值。

    (p++)->num:先求得p->num得值(即10101),然后再使p自加1,指向stu[1].

  • 用结构体变量和结构体变量得指针做函数参数

    #include
    #define N 3
    struct Student
    {
            int  num;//学号
            char name[20];//姓名
            float aver;//平均成绩
            float score[3];//3门课成绩
    };
    void input(struct Student stu[]);//函数声明
    struct Student max(struct Student stu[]);//函数声明
    void print(struct Student stu);//函数声明
    
    void input(struct Student stu[]){
        int i;
        printf("请输入各学生的信息:学号、姓名、三门课成绩:\n");
        for (i = 0; i < N; i++)
        {
            scanf("%d%s%f%f%f",&stu[i].num,stu[i].name,&stu[i].score[0],&stu[i].score[1],&stu->score[2]);
            stu[i].aver=(stu[i].score[0]+stu[i].score[1]+stu[i].score[2])/3.0;
        }
        
    }
    struct Student max(struct  Student stu[])
    {
       int i,m=0;
       for ( i = 0; i <N; i++)
       {
           if (stu[i].aver>stu[m].aver) m=i;   
       }
       return stu[m];
       
    };
    
    void print(struct Student stud){
        printf("\n 成绩最高的学生是:\n");
        printf("学号:%d\n姓名:%s\n三门课成绩:%5.1f,%5.1f,%5.1f\n 平均成绩:%6.2f\n",stud.num,stud.name,stud.score[0],stud.score[1],stud.score[2],stud.aver);
    }
    
    int main(){
        struct Student stu[N],*p=stu;
        input(p);
        print(max(p));
        return 0;
    }
    
  • 什么是共用体类别?

    用同一段内存单元存放不同类型的变量,例如,如一个短整型变量、一个字符型变量、一个实型变量放在同一个地址开始的内存单元中,虽然3个变量再内存中占用的字符不同,但都从同一地址开始存放,也就是使用覆盖技术,后一个数据覆盖了前面的数据。共享同一段内存的结构

    一般形式:union 共用体名{成员表列,变量表列};

  • 说一下共用体和结构体的内存占用区别

    共用体内存是共享的,也就是说里面无论放多少个数据,它门所占的空间之和是最大的那个元素所占的空间,而结构体则是所有空间之和,即结构体内全部的变量所占空间之和

  • 共用体类型数据有什么特点?

    • 同一个内存段可以存放几种不同类型的成员,但再每一瞬间只能存放其中一个成员,而不是同时存放几个。即共用体变量只能存放一个值。

      union Date{
          int i;
          char ch;
          float f;
      }a;
      a.i=97;//最后输出的97
      表示将整数97存放在共用体变量中,可以用以下的输出语句:
      printf("%d",a.i);//输出97
      printf("%c",a.ch);//输出a
      printf("%f,a.f);//输出0.000000
      
    • 可以对共用体变量初始化,但初始化表中只能有一个常量。

      union Date{
          int i;
          char ch;
          float f;
      }a={1,'a',1.5};//错误,不能初始化3个成员,它们占用同一段存储单元
      	union Data a={16};//正确,对第一个成员初始化
      	union Data a={.ch='j'};//C99允许对指定的一个成员初始化
      
    • 共用体常量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原来变量存取单元被取代。

      a.ch='a';
      a.f=1.5;
      a.i=40;
      完成上面3个赋值后,变量存储单元存放的是最后存入的40,原来的'a'1.5都被覆盖了。如果打印,a.ch会打印字符'(',其字节码为40
      
    • 共用体比爱你两的地址和它的各成员的地址都是同一地址。

  • 共用体的应用

#include
/*有若干个人员的数据,其中有学生和教师。学生的数据中包括:姓名、号码、性别、职业、班级。
    教师的数据包括:姓名、号码、性别、职业、职务。
    分析设计:通过题目看出,学生和教师的数据的项目大多数是相同的,只有一项不同,可以使用共用体来处理。
*/
struct 
{
  int num;
  char name[10];
  char sex;
  char job;
  union 
  {
      int clas;//班级
      char position[10];//职务
  }category;
}person[2];

int main(){
    int i;
    for ( i = 0; i <2; i++)
    {
       printf("please enter the data of person:\n");
       scanf("%d %s %c %c",&person[i].num,&person[i].name,&person[i].sex,&person[i].job);
       if (person[i].job=='s')//如果是学生,输入班级
       {
         scanf("%d",&person[i].category.clas);
       }else if (person[i].job=='t')//如果是教师,输入职务
       {
          scanf("%s",person[i].category.position);
       }else{
           printf("Input error!");
       } 
    }
    printf("\n");
    printf("No. name  sex job classs/positon\n");
    for ( i = 0; i < 2; i++)
    {
        if (person[i].job=='s')//若是学生
        {
           printf("%-6d%-10s%-4c%-4c%-10d\n",person[i].num,person[i].name,person[i].sex,person[i].job,person[i].category.clas);
        }else{//若是教师
            printf("%-6d%-10s%-4c%-4c%-10s\n",person[i].num,person[i].name,person[i].sex,person[i].job,person[i].category.position);
        }
    } 
}

  • 枚举类型

    把可能的值一 一列举出来,变量的值只限于列举出来的值的范围内。

    一般形式: enum [枚举名] {枚举元素列表}

    比如: enum Weekday{sum,mon,tue,wed,thu,fri,sat};

    声明方式:enum Weekday workday,weekend;

​ 说明:C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符而把它们看作变量,不能对它们赋值。

​ 2.每一个枚举元素都代表一个整数,C语言编译按定义时的顺序默认它们的值为0,1,2,3,4,5…。在上面的定义中,

​ sum=0,mon=1,…sat=6。如果有赋值语句,wordkday=mon,那么相当于workday=1。

​ 当然也可以人为指定枚举元素的数值,在定义枚举类型时显式地指定,例如:

​ enum Weekday{sum=7,mon=1,tue,wed,thu,fri,sat};

​ 指定sum地值为7,mon地值为1,以后顺序加1,sat为6.

  • 使用typedef声明新类型名

    简单地用一个新的类型名代替原有的类型名,比如命名一个简单的类型名替换复杂的类型表示方法

    1.typedef int  Integer; 
    
    2.typedef命名一个新的类型名代表结构体类型 
    
    typedef struct{
    	int month;
    	int day;
    	int year;
    }Date;
    Date birthday;//不需要再写成struct Date birthday;
    
    3.命名一个新的类型名代表数组类型
    typedef int Num[100];//声明Num为整型数组类型名
    Num a;//定义a为整型数组名,它有100个元素
    
    4.命名一个新的类型名代表指针类型
    typedef char * String;//声明String为字符指针类型
    String p,s[10];//定义p为字符指针变量,s为字符指针数组
    
    5.命名一个新的类型名代表指向函数的指针类型
    typedef int (*Pointer)();//声明Pointer为指定函数的指针类型,该函数返回整型值。
    Pointer p1,p2;//p1,p2为Pointer类型的指针变量
    

    注意:习惯上typedef声明的类型名的第一个字母用大写表示,以便与系统提供的标准类型标识符相区别

  • 说一下#define和typedef的区别

    #define是在预编译时处理的,它只能做简单的字符串替换,而typedef是在编译阶段处理的,做的并不是简单的字符串替换,而是采用如同定义变量的方法那样先生成一个类型名,然后用它去定义变量。


    第十章 对文件的输入输出

  • 在程序设计中,主要用到哪两种文件?

    • 程序文件:源程序文件(.c)、目标文件(.obj)、可执行文件(.exe)
    • 数据文件:文件的内容不是程序,而是供程序运行时读写的数据。(主要讨论数据文件)
  • 什么是文件?

    一般指存储在外部介质上数据的集合

    一批数据是以文件的形式存放在外部介质上的。操作系统是以文件为单位对数据进行管理的。也就是说,如果想找存放在外部介质上的数据,必须先按文件名找到所指定的文件,然后再从该文件中读取数据。要向外部介质上存储数据也必须先建立一个文件,才能向他输出数据。

  • 什么是流?

    首先流是一个抽象的概念,我门常将输入输出形象的称为流,即数据流。流表示了信息从源到目的端的流动。在输入操作时,数据从文件流向计算机内存,在输出操作时,数据从计算机流向文件。文件是由运行环境进行统一管理的。流就是一个传输通道。

    从C程序的观点来看,无论程序一次读写一个字符,或一行文字,或一个指定的数据区,作为输入输出的各种文件或设备都是统一以逻辑数据流的方式出现的。C语言把文件看作是一个字符的序列,即由一个一个字符的数据顺序组成。一个输入输出流就是一个字符流或字节流。

    C的数据文件是由一连串的字符组成,而不考虑行的界限,两行数据间不会自动加分隔符,对文件的存取是以字符为单位的。

  • 文件标识包括哪3部分?

    文件路径、文件名主干、文件后缀

  • 数据文件分为什么?

    ASCII文件和二进制文件,数据在内存中是以二进制形式存在的,如果不加转换的输出到外存,就是二进制文件,可以认为它就是存储在内存的数据的映像,所以也成为映像文件。如果要求在外存上以ASCII代码形式存储,则需要在存储前进行转换。ASCII文件又称文本文件,每一个字节放一个字符的ASCII代码。

  • 一个数据在磁盘上是怎么样存储的呢?

    字符一律以ASCII形式存储,数值型数据即可以用ASCII形式存储,也可以用二进制形式存储。

    用ASCIII码形式输出时字节与字符一一对应,一个字节代表一个字符,因而便于对字符进行逐个处理,也便于输出字符。但一般占存储空间较多,而且需要花费转换时间。用二进制形式输出数值,可以省外存空间和转换时间,把内存中的存储单元中的内容原封不动地输出到磁盘上,此时每一个字节并不一定代表 一个字符。如果程序运行过程中有的中间数据需要保存在外部介质上,以便在需要时再输入到内存中,一般用二进制文件比较方便。在事务管理中,常有大批量数据存放在磁盘上,随时调入计算机进行查询或处理,然后又把修改过地信息再存回磁盘,这时也常用二进制文件。

  • 什么是缓冲文件系统?

    指系统自动地在内存区为程序中每一个正在使用的文件开辟一个文件缓冲区。从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘去。如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区,然后再从缓冲区逐个地将数据送到程序数据库。

  • 文件类型指针

    缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟一个相应的文件信息区,用来存放文件的有关信息(比如文件的名字、文件状态以及文件当前位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是由系统声明的,取名为FILE,不同系统的FILE类型包含的内容不完全相同。

    通过 FILE *fp;

    定义fp是一个指向FILE类型数据的指针变量。可以使fp指向某一个文件的文件信息区,通过该文件信息区中的信息能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。

  • 打开与关闭文件

    fopen(文件名,使用文件方式);

    例如:fopen("a1,“r”);

    表示要打开名字为a1的文件,使用文件方式为读入。fopen函数的返回值是指向a1文件的指针。通常将fopen函数的返回值赋给一个指向文件的指针变量。如 *FILE fp;//定义一个指向文件的指针变量fp fp=fopen(“a1”,“r”);//将fopen函数的返回值赋给指针变量fp

    fclose(文件指针);

    fclose(fp) 把fp指向的文件关闭,此后fp不再指向文件。不关闭可能会丢失数据。

    总过程:在向文件写数据时,先将数据输出到缓冲区,待缓冲区充满后才正式输出给文件。如果当数据未充满缓冲区而程序结束运行,就有可能使缓冲区中的数据丢失。要使fclose函数关闭文件,先把缓冲区中的数据输出到磁盘文件,然后才撤销文件信息区。

  • 标准的流文件 :标准输入流、标准输出流、标准出错输出流

  • 顺序读写数据文件

    fgetc(fp):从fp指向的文件读入一个字符,读成功,带回所读的字符,失败则返回文件结束标志EOF(即-1)

    fputs(ch,fp):将ch写到文件指针变量fp所指向的文件中,输出成功,返回值就是输出的字符;输出失败,则返回EOF(-1)

  • 说一下getchar和putchar的区别

    getchar函数用来接受用户从键盘输入的第一个字符,还有一个作用是吃掉getchar或scanf的回车。

    putchar函数用来将输出的字符显示到屏幕中,

  • 怎么向文件读写一个字符串

    fgets(str,n,fp):从fp指向的文件读入一个长度为(n-1)的字符串,存放到字符数组str中

    fputs(str,fp):把str所指向的字符串写到文件指针变量fp所指向的文件中

  • 用格式化的方法读写文件

    fprintf(文件指针,格式字符串,输出表列);

    fscanf(文件指针,格式字符串,输入表列);

    作用:printf和scanf是对终端而言,而fprintf和fscanf是对于文件而言。其他是一样的。

    例如:fprintf(fp,"%d",i);

  • 用二进制方向向文件读写一组数据

    C语言允许用fread函数从文件中读一个数据块,用fwrite函数向文件写一个数据块。在读写时以二进制形式进行的。在向磁盘写数据时,直接将内存中一组数据原封不动、不加转换地复制到磁盘文件上,在读入时也是将磁盘文件中若干字节内容一批读入内存。

    fread(buffer,size,count,fp);

    fwrite(buffer,size,count,fp);

    buffer:是一个地址,对fread来说,它是用来存放从文件输入的数据的存储区的地址。对fwrite来说,是要把此地址开始的存储区中的数据向文件输出。

    size:要读写的字节数

    count:要读写多少个数据项

    fp:FILE类型指针

  • 随机读写数据文件

    即不是按数据在文件中地物理位置次序进行读写,而是可以对任何位置上地数据进行访问,显然这种方法比顺序访问效率高得多。

    靠地是文件位置标记,可以通过它指向人们指定的位置。使用rewind函数使文件位置标记重新返回文件的开头,此函数没有返回值。

    fseek函数,用来改变文件位置标记,fseek(文件类型指针,位移量,起始点)。起始点可用0,1,2代替,0表示文件开始位置,1表示文件当前位置,2表示文件末尾位置。

    例如:fseek(fp.100L,0) 将文件位置标记向前移动到离文件开头100个字节处

    ftell函数,测定文件位置标记地当前位置.

  • 文件读写的出错检测

    ferror函数,检查输入输出函数调用时可能出现的错误。

    clearerr函数,使文件错误标志和文件结束标志为0

你可能感兴趣的:(C,c语言,vscode,ide)