大一上——C语言程序设计

参考内容主要来自《C语言程序设计》第3版

C语言程序设计概述

1.一个C语言程序的执行是 从程序的main函数开始,到main函数结束的。

2.用C语言编写的代码程序 是一个源程序,它需要经过编译,连接转换成二进制的机器语言才可以被执行。

3.C语言源程序经编译,连接转换成的二进制机器语言 可以直接运行。

4.C语言中的函数可以单独进行编译。

5.C语言程序的运行经过为:编 辑C语言程序源代码;编译形成目标代码;连接目标代码与C语言函数库。将源程序所用的库 代码与目标代码合并,并形成最终可执行的二进制机器程序;运行在特定的机器环境下的C语言程序。

     >  简化来讲就是:编辑源程序;编译源程序;连接目标程序;运行可执行程序。

6.描述算法有很多种方法:自然语言、流程图和N-S图、程序设计语言。下面稍微详细一点介绍一下:
|描述方法|详情 |
|--|--|
|自然语句 |是算法语言的基本表现形式,比较容易理解,但是书写比较繁琐,具有不确切性 |
|伪代码、程序设计语言 |可以把算法准确的表示出来 |
|流程图、N-S图 |用作表示算法较为直观、形象 |

7.算法具有五个特性:有穷性、确定性、有效性、有零个或多个输入、有一个或多个输出(这两点可以概括为输入性和输出性)下面分别介绍:
|特性|特点 |
|--|--|
|有穷性 |是指一个算法应该包含有限的操作步骤,而不是无限的 |
|确定性 |是指算法的每一个步骤都应当是确定的,不应该是含糊的、模棱两可的 |
|有效性 |是指算法中的每一个步骤都应当能有效地执行,并得到确定的结果 |
|输入性 |以刻画运算对象的初始情况,所谓0个输入是指算法本身定除了初始条件 |
|输出性 |以反映对输入数据加工后的结果。没有输出的算法是毫无意义的 |

8.C语言源程序的扩展名是“c”或者“cpp”(cpp是在VC6.0的编译环境中默认的扩展名)这里同时再附加上几个可能会遇见的扩展名;
|扩展名 |属性 |
|--|--|
|txt |文本文件扩展名 |
|obj |编译后产生的目标程序的扩展名 |
|exe |连接后产生的可执行程序的扩展名 |
输入链接地址这里的扩展名是真的全,想了解就戳进去瞅瞅
9.C语言程序的运行结果不对时,大多属于逻辑错误。此时通常采用调试手段,比如:设置断点,或采取单步执行程序,在断点或每条语句处观察变量的值,以确定语句或算法是否正确,以便于修改。

简而言之。调试一般方法:设置断点后观察变量、单步调试后观察变量

10.C语言源程序通常由 编译程序 将其转换为目标程序。

11.根据要执行的动作及这些动作应该执行的顺序求解问题的过程称为 算法。

12.在结构化程序中,有3种结构,分别是:顺序结构、选择结构、循环结构。

13.C语言的关键字通常都是小写字母。

14.C语言称变量、函数、标号、各种用户定义的对象名字为 标识符/标识符的长度从一到若干字符不等,其中第一个字符必须是字母或者下划线,随后的字符只能是字母、数字或者下划线。标识符最长允许32个字符。

C语言的基本数据类型与表达

1.整形常量的表示形式有3种,即十进制、八进制、十六进制,分别有以下的简单特点。
|进制数|特点 |
|--|--|
|十六进制 |对于整形常量,有字母必定是十六进制,且开头一般是“0x/0X”,同时使用%h进行输出表示 |
|八进制 |同样对于整形常量,开头一般是“0”,同时用%o进行输出表示 |
|十进制 |额外说一下%i和%d,如下:|

C语言中 %d 与 %i 的区别 和注意事项

  1. 在 printf 中使用时,没有区别
  2. 在 scanf 中使用时,有点区别,如下:
    ——在scanf格式中,%d 只与十进制形式的整数相匹配。
    ——而%i 则可以匹配八进制、十进制、十六进制表示的整数。·
    ——例如: 如果输入的数字有前缀 0(018、025),%i将会把它当作八进制数来处理,如果有前缀0x (0x54),它将以十六进制来处理。

2.字符常量是用单引号括起来的一个字符,用反斜杠“\”引导的转义字符也是一个字符。对于字符反斜杠“"、单引号“ ' "、双引号" " ",必须再加""引导,形式为'\'、' ' '、' '' '、用八进制或者十六进制ASCII码表示一个字符。形式分别为"\0"引导3位八进制码(如'\107'),和“\x"引导2位十六进制码(如'\xzb')

3.运算符的优先级从高到低的顺序为逻辑非(!)、算术运算符、关系运算符、逻辑运算符“&&”和“||”、条件运算符、赋值运算符、逗号运算符; 结合方向从右至左的运算符有逻辑非(!)、自增自减运算符、条件运算符和赋值运算符。

例:c+++b和(c++)+b 是等价的。
注:在printf函数中的参数。一般是从右往左计算。

输入链接地址(这个是我搜索到比较好比较全面的优先级总结)

4.C语言中不同数据类型所占用的字节是不同的,以下有部分归纳:

如上图,以下有一些备注:

  • 16位编译器:char *(指针变量):2个字节(寻址空间为2^16,所以为两个字节)
    • 32位编译器:char *(指针变量):4个字节
    • 64位编译器:char *(指针变量):8个字节

5.注意“ % ”这个字符,他是表示取整,但是他的对象是整型变量/常量,所以在做题时要谨慎小心被坑(如下)

int main()
{
    double x = 1.42,y;
    y = x % (-3);
    printf("%lf",y);
}

顺序结构程序设计

1.printf函数中用到格式符%5s,其中数字5表示输出的字符串占用5列(即规定了输出字段的宽度)。如果字符串的长度大于5,则输出时,按原字符长从左向右全部输出。

2.scanf函数的转换说明符中可用 域宽 表示要从输入流中读取指定个数的字符或数字。

3.“ - ” (减号)标志使输出在域宽内左对齐。

4.“ + ”(加号)标志显示带有加号或减号的值。

#include <stdio.h>

int main()
{
        int x;
        scanf("%d",&x);
        printf("%+d",x);
}

5.printf函数中用到格式符%10s,其中数字10表示输出的字符串占用10列,如果字符串输入长度大于10,那么输出时仍然按照远字符长从左向右全部输出,如果不大于10,那么默认左对齐。

选择结构程序设计

1.if/else 结构在条件为“真”时执行一个动作,条件为“假”时执行另一个动作。(放这个在这主要是提醒别只写if然后溜了)

2.switch结构用来测试某个特定的变量或表达式是否等于没一个假设的整常数值。

3.对于“||”运算符,一个易错点一定要知道:如果有a=b=c=1,然后if(a++||++b)c++,问b=?,千万不要无脑填完2就走,如果第一个条件就是“真”,那么编译器不会执行++b这一步操作,当且仅当第一个条件为“假”,编译器才会执行第二个条件。所以此处的b还是原来的1,答案b=1。

4.如果程序中有多个if/else结构,那么如何判断哪个if和哪个else组合? 答:else跟它之前还未配对的最近的if组合。

5.sqrt()和fabs(),pow()这几个函数要留意一下,在include<math.h>中。

6.要注意由于运算符带来的坑

int main()
{
    int a=b=c=1;
    if(a++||++b)          //特别留意这个“||”,如果计算机识别到第一个条件为真,那么它是不会进行第二个条件的判断的
        c++;              
    printf("%d",b);       //此时由于a++为真(大于1),所以++b不会执行,所以b还是原来的值:1
}

7.如果有多个if-else结构,就要注意,else是跟其之前未配对的,最近的,if进行配对,如下一个例题很容易出错。

int main()
{
    int a,b;
    scanf("%d%d",&a,&b);
    if(a>b)
        if(a>0)printf("A");
        else if(b>-5)printf("B");
    else printf("C");                //这个else就是跟它上面那个if(b>-5)进行配对的
    printf("*\n");
}

循环结构程序设计

1.只能在循环体内和switch语句体内使用break语句。

2.如果在条件判断时出现这种情况:while(a<b<c),那注意了,这个不是数值运算。 运算方向,从左到右,对这个逻辑表达式,若a<b为“真”,则结果为1,反之为0,然后拿1或0再去和c作比较。上题:

int main()
{
    int a = 1,b = 2,c = 2,t ;
    while(a<b<c)                        //打比赛刷题都没见过这些东西,都不知道考来干啥
    {
        t = a; a = b; b = t;
        c--;
    }
    printf("%d%d%d",a,b,c);
}

函数与编译预处理

1.实参与形参:函数的实参可以是变量、常量、表达式,形参可以是变量或缺省常量(宏定义即def),不可以是表达式。

2.函数返回值的类型最终取决于定义函数时所定义的函数类型。

3.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元,并不是不分配。

4.函数不可以嵌套定义。

5.对于定义成void类型的函数没有返回值。

6.函数不必放在同一程序文件中,只要分别编译后连接起来即可。

7.变量存储类型:
|类型 |特点 |
|--|--|
|静态局部变量 |在函数内定义,只能在定义该变量的函数内使用,退出该函数后,尽管该变量还继续存在,但不能使用它。 |
|动态变量 |在程序运行期间根据需要进行临时动态分配存储空间的变量。 |
|外部变量 |在函数外部定义的变量,它的作用域是整个程序,除了在定义它的源文件中可以使用外,还可以被其他文件使用。 |
|内部变量 |在函数内部定义的变量,作用域为定义变量的函数,也就是说它只能在定义的函数中使用 |
8.函数的定义形式包括类型标识符、函数名及括号、形参列表、函数体。形参列表中个参数之间用逗号间隔。

例:y = func(a,b,max(d,e)); 问实参几个?答:3个。 max(d,e)算1个,因为形参以逗号形式间隔。

9.直接递归调用和间接递归调用,这俩兄弟是啥关系?
|调用类型 |特点 |
|--|--|
|直接递归调用 |调用的是函数(自己)本身,相当于funA(…………funA += 2;…………)dddd ,且在A函数中嵌套使用A函数然后有一个停止该函数的条件 |
|间接递归调用 |调用的是其他函数,相当于funA(…………funA = funB * 2;…………)dddd ,在A函数中调用B函数,然后在B函数中调用A函数,实现递归 |

10.一个完整的C语言函数包括 函数说明部分 和 函数体。

11.C语言的函数参数传递有两种方式

  • 值传递
  • 指针传递

由于实参传递给形参,两者之间就断开了关系,具有单向性,也称为单向值传递。

数组

1.数组占用内存的字节数和其中目前所存储的元素数无关,只和他本身定义时的容量大小有关。

例:int a[6] = {1,3,3},数组a占用内存为6 * 2 = 12字节。

2.C语言定义数组的时候不允许用变量,只可以用常量(虽然C++的动态数组真的很香,但这里不能使用

    int n = 1e5;
    int a[n];                     //这就是上面所说用变量来定义数组,C语言不允许这种操作

    //但是c语言也可以定义动态数组(用malloc或者calloc)
    #include <stdio.h>
    #include <stdlib.h>
    int main ()
    {
        int n;
        int *p;
        scanf("%d",n);
        p=(int *)malloc(n*sizeof(int))
        for(i=0;i<n;i++){
            printf("%d",p[i]);
        }
        return 0;
    }

    #define SIZE 1e5
    int a[SIZE];                  //这是用常量定义数组,符合C语言的规定

3.C语言初始化二维数组的时候, 可以不指定第一维的大小,但是第二维大小必须指定。C语言编译系统可以自动根据初值数目与第二维大小(列数)自动确定第一维大小。

例:合法:static int a[][2] = {1,2,3,4,5,6}; ————非法:static int a[2][] = {{1,0,1}.{5,2,3}};

4.一个二维数组,a[ b ][ c ]是代表着这个二维数组有b行c列,初始行、列的下标是0,行的最大下标是b-1,列的最大下标是c-1 。

5.字符串数组和字符数组是有区别的:字符串数组存储时,结尾包含了字符串结束符“\0",所以存储字节数应该是字符长度+1,相比单纯存储字符的数组,字节数多1 。

6.字符数组存放的字符串可以整体输出输出,不可以用关系运算符对字符数组中的字符串进行比较。

7.在C语言中,字符串借助字符型一维数组来存放数据,规定以字符“\0"作为字符串结束标识。而且‘\0'不记入字符串的长度。字符串中以''引导的字符称为转义字符,可以作为一个字符看待。

例:char c[] = {\tv\0will\n};printf("%d",strlen( c ));输出结果是? 答:3

8.gets函数调用的形式为“gets(字符串地址)"。

9.字符数组存放的字符串可以整体输入输出。

10.不可以用关系运算符对字符数组中的字符串进行比较。

11.在用scanf输入字符串的时候,系统将输入的字符串的各个字符按顺序赋给字符数组s的各元素,直到遇到回车符或者空格符为止,并自动在字符串末尾补上字符串结束标识符"\0"。而此时使用gets()函数就不会产生这种问题,gets() 会自动在末尾加字符串标志符"\0",输入字符串时以回车结束输入这种方式可以读入含空格符的字符串。

12.顺便附上一些字符串处理函数

  • strcpy(str1,str2) 将字符串str2复制到字符数组str1中,str2值不变。
  • strcat(str1,str2) 将str2连同"\0"连接到最后一个非"\0"字符后面。连接后的新字符串存到str1中。
  • strcmp(str1,str2) 若str1 = str2 ,则函数返回值为0,若str1 > str2 ,则函数返回值为正整数,若str1 < str2,则函数返回值为负整数。
  • strlen(字符串) 求字符串的实际长度(不包含"\0"),由函数值返回。

**

指针

**
1.定义指针变量p和q分别指向n2和n1,有关”“和”&”的一些表达如下:
|形式 |表达含义 |
|--|--|
|
p |表示的是n2的值 |
|*q |表示的是n1的值 |
|p |表示的是n2的地址 |
|q |表示的是n1的地址 |
|&n1 |表示的是n1的地址 |
|&n2 |表示的是n2的地址 |

2.对p 和 *pp的区别:

    #include <stdio.h>
    int main()
    {
        int a = 22,*p,**pp;
        p = &a;
        pp = &p;
        printf("*p = %d\n",*p);
        printf("**pp = %d\n",**p);
    }
    \\输出结果
    *p = 22
    **pp = 22

p是直接指向a,p是 一级指针 引用。pp是直接指向p,再通过p指向a,pp间接指向a,*pp是 二级指针 引用。
3.指针的算术运算:

p = &a;

假设a的起始地址是4000,请计算p = p + 2;之后,p的值?
p + 2 表示指针向下移两个整型变量的位置,所以p的值为 4000 + 2 * sizeof(int) = 4000 + 2 * 2 = 4004
由于整型变量占2个字节,所以结果不是4002

p = p + n 表示p向高地址方向移动n个存储单元快(一个单元快是指指针所指变量所占存储空间)
p++若作为操作数,则是先引用p,再将p向高地址方向移动一个存储单元快,++p则是顺序相反,p--和--p同理

4.一个指针变量只能指向同一类型的变量

5.一个变量的地址称为该变量的指针

6.两个同数据类型的指针变量不可以作加减运算,但是两个相同数据类型的指针变量可以相互赋值。

7.任何指针变量都可以与空指针NULL进行比较,比如:

    #include<stdio.h>
    pa = NULL;

这里指针pa并不是指向0地址单元,而是具有一个确定的定值,表示pa不指向任何变量。同时也要注意的是,指针虽然可以赋值0,但却不能把其他的常量地址赋值给指针。举个例子说明:

假设整型变量a的地址是4000,下面这种赋值方式是错误的:

int a;
    pa = 4000;

只能用下面这种方式表示:

 int a;
    pa = &a;

8.现有 int (*ptr)[M] ,那么标识符ptr表示一个指向具有M个整型元素的的一堆数组的指针。

9.若两个指针变量指向同一个数组的不同元素,则可以进行减法运算和 比较运算 。

10.存放某个指针的地址值的变量称为指向之指针的指针,即 二级指针 。

11.现有下面的例题

    char a[15] = "Windows-9x";
    printf("%s",a + 8);

请问结果?答:9x,就是个细节题,提醒注意%c和%s的区别。

12.对于二维数组与多维数组的指针表示法。主要注意:

任意元素a[ i ][ j ]的地址可以表示为a[ i [ + j 或 *(a + i ) + j ,而元素值则表示为 * (a[ i ] + j ) 或 * ( * (a + i) + j )。

例如:

  • a[0][2]元素可以表示为 * ( * a + 2 ) 或 * ( a[0] + 2)。
  • a[2][1]可表示为 * (a[2] + 1) 或 * ( * (a + 2) + 1)。
  • 注意区分一个二维数组元素的3种表示形式:a[ i ][ j ]、 * (a[ i ] + j) 、 * ( * (a + i) + j )。

结构体

1.“ . "称为 成员 运算符,“->"称为 指向成员 运算符。

2.结构体定义方法:

struct 结构体类型名
{
成员说明表列
}变量名表列

有以下需要注意:

  • 类型与变量是不同的概念。只能对变量赋值、存取或运算,而不能对一个类型赋值、存取或运算。
  • 对结构体中的成员,可以单独使用,它的作用与地位相当于普通变量。
  • 成员也可以是一个结构体变量。
  • 成员名可以与程序中的其他变量名相同,两者不代表同一对象。

3.结构体类型所占的内存空间是其所有成员所占的内存空间之和。而共用体类型实际占用存储空间为其最长成员所占用的内存空间。

4.指向结构体的指针变量引用结构体成员的标记方法有两种

  • 指针变量->结构体成员名
  • ( * 指针变量).结构体成员名

5.共用体类型的定义形式方式:

    union 共用体类型名
    {
    成员说明列表
    };
    //经常使用的定义共用体类型变量的方式如下:

    union
    {
        int i;
        char eh;
        float f;
    }a,b,c;

需要注意的是,一个共用体变量不是同时存放多个成员的值,而只能存放其中的一个成员的值,就是最后赋给它的值。例如: a.i=278 , a.eh = 'D' , a.f = 5.78 共用体变量中最后的值是 5.78 。

这里给个例题可以很清晰理解

    #include<stdio.h>
    union eg1
    {
        int c;
        int d;
        struct{int a,b;} out;
    }e;
    int main ()
    {
        e.c = 1;
        e.d = 2;
        e.out.a = e.c * e.d;
        e.out.b = e.c + e.d;
        printf("%d,%d\n",e.out.a,e.out.b);
    }
    //执行结果是  4,8
    //这里说一下大概过程:
    //e.d是最后给共用体赋值的,因此“共用体的值”应该是2,在进行后面运算的时候,e.out.a就是2*2 = 4
    //同样的,最后给共用体赋的e.out.a,因此“共用体的值”应该是4,所以才会有最后的e.out.b = 4 + 4 = 8 。

6.枚举类型定义的一般形式为:

enum 枚举类型名 {标识符1,标识符2,…… 标识符n };

有以下需要注意的:

  • enum是关键字,表示枚举类型,定义枚举类型必须以enum开头
  • 在定义枚举类型时,花括号中的名字称为枚举元素或者枚举常量
  • 枚举元素不是变量,不能改变其值。且从花括号的第一个元素开始,值从0开始以1为等差数列递增,除非遇到定义时已经赋值的情况(enum{ren = 3,yellow,blue=999,black})
  • 枚举常量可以进行比较
  • 一个枚举变量的值只能是这几个枚举常量之一可以将枚举常量赋给一个枚举变量,但不能将一个整数赋给他(color = black正确,但是color = 5就是错误的)
  • 枚举常量不是字符串,不能用下面的方法输出字符串( printf("%s",red);)如果想要先检查color的值,若是red就输出字符串“red”,可以使用语句:
    color = red;
    if(color == red)
     printf("red");