C语言基础精炼

前言:

  • 这是一篇有关C语言语法基础的精炼概述性质文章,不会涉及C语言一些有深度的内容
  • 本文面向的是以大学C考试过关为目标,以计算机二级认证为目标,期望获得一个C语言最基础认识的读者
  • 博主能力有限,可能某些知识点未能讲解清晰,某些知识点遗漏,甚至某些知识点错误,若发现以上问题欢迎邮件勘误。

Start

C语言基本结构

1
2
3
4
5
6
7
#include<stdio.h>

int main(){
    /* 这是每个程序的开始 */
    printf("Hello World!\n");   //这是另一种注释方法
    return 0;
}
  • 所有C语言程序都包含一个main()函数,程序从main()函数开始执行,每个函数用花括号“{}”包括。

  • C语言有两种添加注释方法

    1. 以“//”开头的单行注释
    2. 以“/**/”包裹的多行注释,或者叫块注释
  • stdio.h是一个头文件(标准输入输出头文件,这个头文件内包含printf函数的定义与实现,如果没有找到stdio.h,printf函数会出现编译错误)。

  • #include 是一个预处理命令(c语言中以#开头的命令称为预处理命令,类似还有#define)用来引入头文件。

  • return 0; 用于退出程序,并向操作系统返回一个数值0.

  • 每一句完整的c语言代码用分号“;”(英文小写)作为结束符号


C语言简介

  • 百度百科

  • 维基百科

    重点

    1. C语言是一门面向过程高级语言
    2. C语言仍然保持跨平台特性
    3. 未完待续。。。

编写环境

  • 这里仅为初学者,没有开发项目需求的读者推荐以下开发软件

  • 软件名称超链接对应的软件的基本操作入门,Download超链接为下载地址。这里提供的软件下载地址部分是国外网站,如果无法打开可以百度软件名下载

    软件简介链接
    Dev C++个人觉得最适合新手的集成开发环境,同时具有简单项目的开发能力。傻瓜式安装即点即用
    同时支持中文,缺点大概是无法跨平台(需要跨平台的也就不是新手了)
    Download
    Code Blocks很多文章鼓吹的集成开发环境,博主本人没有使用过,但是口碑一直不错。
    作为一款开源软件同时支持跨平台就是其优点
    Download
    Visual C++ 6.0微软老牌集成开发环境,个人感觉几近过时是前计算机二级指定开发环境
    除非是学习单片机开发的,或者熟悉二级考试环境,不建议使用
    Download
    Visual Studio 2010微软集成开发环境,全国计算机二级新标准指定的软件Download
    Visual Studio (latest)不是很推荐新手使用最新版的Visual Studio,
    微软最新的vs对诸多c++语法做了安全方面的限制
    No Download

基本数据类型

  • 在了解基本数据类型之前我们需要对计算机存储空间单位换算有个基本概念

    • 最小单位bit(位):即一个二进制位
    • Byte(字节):1 Byte=8 bit
    • KB(千字节):1 KB=1024 Byte
    • MB(兆字节):1 MB=1024 KB
    • GB(吉字节):1 GB=1024 MB
    • TB(太字节):1 TB=1024 GB
  • 更多相关资料链接


  1. 整数类型

    类型存储大小取值范围
    char1 byte-2^7~2^7-1
    int2 byte(32bit)
    4 Byte(64bit)
    -2^152^15-1
    -2^31
    2^31-1
    short2 byte-2^15~2^15-1
    long4 byte-2^31~2^31-1
    • int型在32位程序和64位程序中所能表示的范围是不一样的,所以表中会有两个范围.

    • 重点:

    结合之前对存储单位的科普。我们知道1 Byte=8bit即1字节等于8个比特位。计算机内部其实只认识1和0,也就是二进制。所以计算机对任何数据的处理都是转换成对应的二进制。1字节对应8个二进制位即:

    1
    
    00000000 -- 11111111
    

    用8个位子随意组合摆放0,1可以由2^8种组合,计算机内部将这2^8种组合在一一映射到实际的数值上。列如:

    1
    2
    3
    4
    5
    6
    7
    
    00000000 --> -2^7=-128
    00000001 --> -2^7+1=-127
            ···
    10000000 --> 0
    10000001 --> 1
            ···
    11111111 --> 2^7-1=127
    

    所以我们就有了

    1
    
    char 1 Byte(字节)范围 -2^7~2^7 即 -128~127
    

    其他类型也可以同样的方法计算范围。也正如表中所给的数值范围,全部以2的次幂记忆。如:

    1
    2
    3
    4
    5
    6
    
    int 在64位下 大小为4Byte(字节)
    4 Byte=4*8 bit=32bit 
    也就是32个二进制bit位
    所以int型的范围可以表示为
    -2^31~2^31-1
    (有没有奇怪为森魔要-1?因为数字0的存在)
    
    • unsigned & signed

    值得一提的是c语言种对基本数据类型还有两个修饰符可以使用:

    1
    2
    
    unsigned    中文释义:无符号
    signed      中文释义:有符号
    

    顾名思义,举例来说,如果我们使用unsigned修饰int型

    1
    2
    
    unsigned char x;
    /* 如此声明的这个变量x的取值范围就会变成 0~2^8-1 即 0-255 */
    

    而signed有符号,以上常用数据类型默认都是由signed(有符号修饰的)所以他们的范围从负数开始,如果你加上unsigned(无符号)那么所有的数据类型范围将是从0开始


  2. 浮点类型

    类型存储大小取值范围精度
    float4 Byte1.2E-38 ~ 3.4E+386位小数
    double8 Byte2.3E-308 ~ 1.7E+30815位小数
    long double16 Byte3.4E-4932 ~ 1.1E+493219位小数
    • 有关浮点型的相关问题属于较有深度的问题,这里不做探究

  3. void类型

    void类型呢是一个比较特殊的类型。

    • 用来修饰函数表明函数返回值位空,即不存在返回值
    1
    2
    3
    4
    5
    6
    7
    8
    
    #include<stdio.h>
    void main(){
        printf("hello world");
        return;
    }
    /*
        还记得return吗,用于结束函数并向操作系统或者上级调用函数返回一个数值。如果这个函数是用void修饰的,列如这里的main()函数,这里return就不必返回一个数值(如果你还是写作return 0;还会报错)
    */
    
    • 用来修饰函数参数表明该函数不接受参数
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    #include<stdio.h>
    int func(void){
        printf("hallo world");
        return 0;
    }
    int main(){
        func();
        return 0;
    }
    //其实默认括号内为空就表示没有参数,即void是可以省略的
    
    • 用来修饰指针代表对象地址,而不是一个类型。(关于指针会在指针部分详解)

  4. 常量

    • 常量,是写死在程序里,在程序运行过程中不可更改的量

    • 定义方法

    1
    2
    3
    4
    5
    6
    7
    8
    
    //所有基本数据类型均可以定义对应常量
    #define N 100       
    #define Good true
    #define x 1.2345
    #define newline '\n'
    
    //你也可以使用那个const关键字定义常量
    const double x=0.123456;
    

基本输入输出语句

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
int main(){
    int i;      //声明整形变量i
    int x,y;    //声明整形变量x,y
    float a;
    double b;
    char ch;    //声明字符变量ch

    scanf("%d",&i);         //从键盘输入数字i
    scanf("%d %d",&x,&y);   //从键盘同时输入数字x,y
    scanf("%f %lf",&a,&b);
    scanf("%c",&ch);        //从键盘读入一个字符ch
        
    printf("hallo world\n");  //打印固定字符串
    printf("%d\n",i);         //打印单个数字
    printf("你输入的x=%d,y=%d\n",x,y);//格式化输出
    printf("单精度a=%f;双精度b=%.3lf",a,b);//打印输出浮点类型,并限制位数
    printf("%c",ch);//打印单个字符
    return 0;
}
  • 以下是样例输入

    1
    2
    3
    
    10
    2 3
    1.234 3.141592654
    
  • 以下是输出

    1
    2
    3
    4
    
    hallo world
    10
    你输入的x=2,y=3
    单精度a=1.234000;双精度b=3.142
    
    • 你可能会奇怪不是应该还输入一个单个字符吗,为什么没有了,其实那个单个字符就是两个小数最后的回车符所以你也看到了我的输出其实多了一行,对那就是那个最后输入的回车符。
    • "%.3f"这个写法是限制小数后打印三位,默认四舍五入

    请努力理解以上代码,结合下方给出的资料

    • 格式输出符号
    格式输出符号含义
    %a浮点数、十六进制数字和p-记数法(c99
    %A浮点数、十六进制数字和p-记法(c99)
    %c一个字符(char)
    %C一个ISO宽字符
    %d有符号十进制整数(int)(%ld、%Ld:长整型数据(long),%hd:输出短整形。)
    %e浮点数、e-记数法
    %E浮点数、E-记数法
    %f单精度浮点数(默认float)、十进制记数法(%.nf  这里n表示精确到小数位后n位.十进制计数)
    %g根据数值不同自动选择%f或%e.
    %G根据数值不同自动选择%f或%e.
    %i有符号十进制数(与%d相同)
    %o无符号八进制整数
    %p指针
    %s对应字符串char*(%s = %hs = %hS 输出 窄字符)
    %S对应宽字符串WCAHR*(%ws = %S 输出宽字符串)
    %u无符号十进制整数(unsigned int)
    %x使用十六进制数字0xf的无符号十六进制整数
    %X使用十六进制数字0xf的无符号十六进制整数
    %%打印一个百分号
    %I64d用于INT64 或者 long long
    %I64u用于UINT64 或者 unsigned long long
    %I64x用于64位16进制数据
    • 基本常用的就是整数,小数,字符,字符串的输出。

    • 转义字符

    转义字符含义ASCII码值
    \a响铃(BEL)007
    \b退格(BS) ,将当前位置移到前一列008
    \f换页(FF),将当前位置移到下页开头012
    \n换行(LF) ,将当前位置移到下一行开头010
    \r回车(CR) ,将当前位置移到本行开头013
    \t水平制表(HT) (跳到下一个TAB位置)009
    \v垂直制表(VT)011
    \\代表一个反斜线字符'''092 ​
    '代表一个单引号(撇号)字符039
    "代表一个双引号字符034
    \0空字符(NULL)000
    \ddd1到3位八进制数所代表的任意字符三位八进制
    \xhh1到2位十六进制所代表的任意字符二位十六进制
    • 难点

    • scanf()函数,以空格或者回车符作为截断。

    • C语言中的输入输出缓冲区机制

      • 在C/C++中,输入输出事实上是各自有一个缓冲区的。缓冲区故名思意。

      • 在你的键盘,屏幕和程序实际获得输入之间还有一个缓冲区。

      • 你按下的按键会被先存放到缓冲区内,接着程序从输入缓冲区读取。处理完毕之后将输出写在输出缓冲区内,屏幕再从输出缓冲区内读取输出并显示给你。

      并不是你想象中的你按下的每一个字符都会直接被程序接收

    • 当再输入的时候涉及到了字符,或者字符串,并且你发现输入的数据并没有按照你的需要,按照你所想的进行,多半是因为在输入缓冲区内有你之前输入操作时遗留的字符在里面(简单举例,你使用scanf输入一个数字并且按下回车,数字被程序从输入缓冲区读取,但是遗留下来了一个回车符,如果你紧接着读取一个字符,就会出现你意料之外的情况,也就是上述输入输出示例代码中的情况。)

    • 当输入缓冲区出现问题时可以使用如下代码清除缓冲区:

      1
      
      fflush(stdin);
      

      然而并不是所有的编译器支持这个函数


运算符

  1. 算术运算符

    这里假设A=10,B=20

    运算符含义实列
    +把两个操作数相加A + B 将得到 30
    -从第一个操作数中减去第二个操作数A - B 将得到 -10
    *把两个操作数相乘A * B 将得到 200
    /分子除以分母B / A 将得到 2
    %取模运算符,整除后的余数B % A 将得到 0
    ++自增运算符,整数值增加 1A++ 将得到 11
    --自减运算符,整数值减少 1A-- 将得到 9
    • 重点

    自增自减运算符的理解

    • 自增自减少运算符仅支持整数类型
    • 逻辑关系
      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      #include<stdio.h>
      int main(){
          int x=0;
          printf("%d\n",x++);
          printf("%d\n",x);
          printf("%d\n",--x);
          printf("%d\n",x);
          return 0;
      }
      
      样例输出
      1
      2
      3
      4
      
      0
      1
      0
      0
      
    • 当这两个运算符出现在程序语句中时
    • 如果位于变量前,则先执行自增(或自减)再执行程序语句
    • 如果位于变量之后,则先执行程序语句,再进行自增(或自减)

    除此之外c语言还支持如下的写法简化:

    1
    2
    3
    4
    
    i=i+1;  可以写作 i+=1;
    i=i*10; 可以写作 i*=10;
    i=i/2;  可以写作 i/=2;
    i=i-5;  可以写作 i-=5;
    

  2. 关系运算符

    这里假设A=10,B=20

    运算符描述实例
    ==检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 不为真。
    !=检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
    >检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 不为真。
    <检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
    >=检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 不为真。
    <=检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。

  3. 逻辑运算符

    这里假设A=10,B=20

    运算符描述实例
    &&称为逻辑与运算符。如果两个操作数都非零,则条件为真。(A && B) 为假。
    ||称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。(A
    !称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。!(A && B) 为真。

  4. 按位运算符

    位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:

    pqp & qp | qp ^ q
    00000
    01011
    11110
    10011

    假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

    1
    2
    3
    4
    5
    6
    7
    
    A = 0011 1100
    B = 0000 1101
    -----------------
    A&B = 0000 1100
    A|B = 0011 1101
    A^B = 0011 0001
    ~A  = 1100 0011
    
    • c语言中实际的位运算符
    位运算符描述实列
    &按位与操作,按二进制位进行"与"运算。(A & B) 将得到 12,即为 0000 1100
    |按位或运算符,按二进制位进行"或"运算。(A | B) 将得到 61,即为 0011 1101
    ^异或运算符,按二进制位进行"异或"运算。(A ^ B) 将得到 49,即为 0011 0001
    ~取反运算符,按二进制位进行"取反"运算。(~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。
    <<二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。A << 2 将得到 240,即为 1111 0000
    >>二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。A >> 2 将得到 15,即为 0000 1111

条件选择语句

  • c语言把任意非零,非空的值定义为true,把零或null定义为false
  1. 基本if语句

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    #include<stdio.h>
    int main(){
        if (1) printf("hallo ");
    
        if (true) {
            printf("world");
        }
    
        int x=10;
        if (x) printf("\n");
    
        //求a,b,c种的最大值
        int a=5,b=2,c=3;
        int ans;
        if (a>b){
            ans=a;
        }
        else{
            if (b>c){
                ans=b;
            }
            else{
                ans=c;
            }
        }
        printf("a=%d,b=%d,c=%d\nMax=%d\n",a,b,c,ans);
    
        //    &&且    ||或
        if (a>b && b>c) printf("a\n");
        if (a>b || b>c) printf("b\n");
    
        return 0;
    }
    

    输出如下

    1
    2
    3
    4
    
    hallo world
    a=5,b=2,c=3
    Max=5
    b
    
    • 重点

    c语言的条件判断语句具有短路性质

    • 在用&&连接的两个表达式A,B中。如果A的运算值为false,那么表达式B不会运算
    • 在用||连接的两个表达式A,B中。如果A的运算值为true,那么表达式B不会运算
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    #include<stdio.h>
    int main(){
        int a=1,b=2,c=3;
    
        if (a>b && ++a) printf("Yes\n");
        //这里由于a>b不成立,所以a++不会被执行,所以a的值仍然为1,当然prinft语句也不会被执行
        if (a<b && ++a) printf("%d\n",a);
        //这里由于a<b成立,所以a++也会被执行,这里会运行输出a数值2
    
        if (b<c || ++b) printf("%d\n",b);
        // ||也是如此,由于b<c成立,于是b++也不会被执行,输出b的数值为2
        return 0;
    }
    

    样例输出

    1
    2
    
    2
    2
    

  2. 三元运算符

    1
    2
    3
    4
    5
    6
    7
    8
    
    #include<stdio.h>
    int main(){
        int n;
        printf("请输入一个数字:");
        scanf("%d",&n);
        (n>0)?printf("正数\n"):printf("负数\n");
        return 0;
    }
    

    样例输入

    1
    
    请输入一个数字:12
    

    样例输出

    1
    
    正数
    
    • 如上的三元运算符可以近似写成如下的if语句:
    1
    2
    3
    4
    5
    6
    
    if (n>0) {
        printf("正数");
    }
    else {
        printf("负数");
    }
    

  3. switch语句

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    #include<stdio.h>
    int main(){
        int n;
        printf("请输入一个数字:");
        scanf("%d",&n);
        switch (n){
            case 1: printf("this is case 1\n"); 
            case 2: printf("this is case 2\n"); break;
            default:
                printf("this is default\n");
        }
        return 0;
    }
    

    样例输入1

    1
    
    1
    

    样例输出1

    1
    2
    
    this is case 1
    this is case 2
    

    样例输入2

    1
    
    2
    

    样例输出2

    1
    
    this is case 2
    

    样例输入3

    1
    
    3
    

    样例输出3

    1
    
    this is default
    
    • 重点

    • 每一个case语句分支必须以break;(即返回)结尾,否则case的条件分支都会继续往下执行。


循环语句

  1. for循环

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    #include<stdio.h>
    int main(){
        //  统计1加到100的和
        int i;
        int ans=0;
        for (i=0;i<=100;i++){
            ans=ans+i;
        }
        printf("%d\n",ans);
        return 0;
    }
    

    样例输出

    1
    
    5050
    

    下面是 for 循环的控制流:

    1
    2
    3
    4
    
    for ( init; condition; increment )
    {
        statement(s);
    }
    
    • init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
    • 接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。
    • 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
    • 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。

    在VC 6.0中不支持在init中声明循环控制变量,只允许使用上面的样例代码,否则会有

    1
    
    [Error] 'for' loop initial declarations are only allowed in C99 or C11 mode
    

    错误,在比较新版本的编译器中,如下写法是被支持的

    1
    2
    3
    
    for (int i=0;i<=100;i++) {
        ans=ans+1;
    }//并且,这里的i变量在循环体结束之后就会被释放销毁,即你在循环体之外是无法再次使用i变量的。
    

  2. while循环

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    //求1~100内奇数的和
    #include<stdio.h>
    int main(){
        int i=1;
        int ans=0;
        while (i<=100){
            ans+=i;
            i+=2;
        }
    }
    

    执行逻辑

    1
    2
    3
    4
    
    while(condition)
    {
    statement(s);
    }
    
    • 在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。

    • condition 可以是任意的表达式,当为任意非零值时都为 true。当条件为 true 时执行循环。 当条件为 false 时,退出循环,程序流将继续执行紧接着循环的下一条语句。


  3. do while循环

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    //计算100以内偶数的和
    #include<stido.h>
    int main(){
        int i=2;
        int ans=0;
        do{
            ans+=i;
            i+=2;
        }while(i<=100)
    }
    

    执行逻辑

    1
    2
    3
    4
    5
    
    do
    {
    statement(s);
    
    }while( condition );
    
    • 与while循环不同的是,条件表达式出现在循环的尾部,所以循环中的 statement(s) 会在条件被测试之前至少执行一次。

    • 如果条件为真,控制流会跳转回上面的 do,然后重新执行循环中的 statement(s)。这个过程会不断重复,直到给定条件变为假为止。


函数

  1. C语言中的函数声明:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    
    返回值类型 函数名(参数类型 参数1,参数类型 参数2 ···){
        函数主体
    }
    
    实列:
    //求a,b中的最大值,并返回该最大值
    int Max(int a,int b){
        int ret;
        (a>=b)?ret=a:ret=b;//还记的这个三元运算符吗?
        return ret;   
    }
    

  2. 函数调用

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    #include<stdio.h>
    int Max(int a,int b) {
        int ret;
        (a>=b)?ret=a:ret=b;
        return ret;
    }
    
    int main() {
        int a=10,b=20;
    	printf("%d\n",Max(a,b));
    	return 0;
    }
    

    样例输出

    1
    
    20
    

  3. 函数参数

    • 值传递

    • c语言默认多数为是传值调用,把参数的实际值赋给函数内的形式参数,在这种情况下在函数内部修改形式参数的值并不会影响实际参数,例子:

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      
      #include<stdio.h>
      //函数作用:交换ab的值,这是交换两个数常用写法。
      void swap(int a,int b){
          int t;
          t=a;
          a=b;
          b=t;
      }
      int main(){
          int a=10,b=20;
          printf("a=%d,b=%d\n",a,b);
          swap(a,b);
          printf("a=%d,b=%d\n",a,b);
          return 0;
      }
      

      样例输出

      1
      2
      
      a=10,b=20
      a=10,b=20
      
    • 如你所见,默认情况下在函数内部对传入参数做修改并不会影响实际参数的值

    • 如果你想真的向修改传入参数的值,那么就要使用下面将会提到的引用传递(引用传递的实质就是指针的运用)

    • 引用传递

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    #include<stdio.h>
    void swap(int *a,int *b){
        int t=*a;
        *a=*b;
        *b=t;
    }
    int main(){
        int a=10,b=20;
        printf("a=%d,b=%d\n",a,b);
        swap(&a,&b);
        printf("a=%d,b=%d\n",a,b);
        return 0;
    }
    

    样例输出

    1
    2
    
    a=10,b=20
    a=20,b=10
    

  1. 作用域规则

    • 作用域指程序定义的变量所存在的区域,超过这个区域,变量就不能被访问。C语言中我们可以在三处地方声明变量:

      1. 在函数或者块(这里的块可以是for循环那个的init还记得吗)内部的局部变量
      2. 在所有函数外部的全局变量
      3. 在函数参数中定义的形式参数变量
    • 程序查看变量存在并访问的优先级顺序永远是先局部再全局(在某种程度上我们也可以把函数参数声明中定义的形式参数也理解为一种局部变量)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    #include<stdio.h>
    //全局变量
    int g=10;
    
    void func1(int g){
        printf("in func1 g=%d\n",g);
        return;
    }
    
    void func2(){
        printf("in func2 g=%d\n",g);
        return;
    }
    
    int main(){
        //局部变量
        int x=20,g=30;
        printf("in main x=%d,g=%d\n",x,g);
        func1(x);//我们把x=20的值赋值给形式参数g
        func2();
        return 0;
    }
    

    样例输出

    1
    2
    3
    
    in main x=20,g=30
    in func1 g=20
    in func2 g=10
    
    • 在main()函数中尝试访问x,g的值并输出显示,程序优先查看局部变量中是否存在x,g,存在于是输出
    1
    
    in main x=20,g=30
    
    • 因为局部变量声明了一个g,所以这里并不会访问全局的g
    • 在func1()中我们尝试访问变量g,因为变量g在函数参数部分存在声明,是一个形式参数。所以也不会去访问全局变量g。于是输出
    1
    
    in func1 g=20
    
    • 即传入的x的值。
    • 在func2()中,我们没有在函数内部声明任何局部变量,形式参数,于是这里尝试访问变量g就是访问的全局变量的值,输出
    1
    
    in func2 g=10
    
    • 重点

    • 全局变量被保存在内存的全局存储区中,占用静态存储空间,并且会被初始化。

    • 局部变量被保存在一种称为栈的结构中,只有在函数被调用的时候才会真正的被分配存储空间

    • 局部变量被定义的时候,系统不会对其进行初始化(虽然多数情况下不初始化数值也多为0)

    • 全局变量系统会自动进行初始化,初始化值如下

    数据类型初始化默认值
    int0
    char'\0'
    float0
    double0
    pointer(指针)NULL

进阶数据结构

  1. 数组

    • 数组,是可以存储大小固定类型相同并且个数固定的顺序集合。

    • 声明方法:

      1
      
      数据类型 数组名称[数组大小];
      

      示例:

      1
      2
      3
      4
      
      int a[100];
      /*
      这个实例里我们就定义了一个长度为100的数组,可以存储100个int型数据
      */
      

      注意:C语言中的数组下标从0开始,这里我们定义了一个长度为100的数组a,但是其下标范围事实上是从0到99


    • 数组初始化

      1
      2
      3
      4
      5
      6
      7
      8
      9
      
      int a[10]={0};
      //声明一个大小为10的整形数组,并初始化所有元素为0
      //还记得吗,下标从0开始哦,0~9
      
      double a[]={0.0,1.1,2.2,3.3}
      //你也可以为数组里每个元素单独初始化,如果你这么做的话还可以省略方括号里的数组大小(这个数组大小为4)
      
      int a[10]={0,1,2,3}
      //如果规定了数组大小但是没有把每个元素单独赋值,未赋值部分依据上面提到的默认初始化规则初始化。
      

    • 数组元素访问

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      
      #include<stdio.h>
      int main(){
          //声明一个大小为100的int型数组并初始化所有值为0
          int a[100]={0};
      
          //将数组所有元素赋值为1~100
          for (int i=0;i<100;i++){
              //我们通过 数组名[下标] 的方式访问数组元素
              a[i]=i+1;
          }
      
          //我们计算数组里所有元素的累加和,即1-100的累加和
          int ans=0;
          for (int i=0;i<100;i++){
              ans+=a[i];
          }
          //样例输出5050
          printf("%d\n",ans);
          return 0;
      }
      

    • 数组在函数之间的传参

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      #include<stdio.h>
      //计算a数组内所有元素的平均值
      double func(int* a,int size){
          double average=0;
          for (int i=0;i<size;i++)
              average+=a[i];
          return average/(double)size;
      }
      /*
          其实你可以发现数组传参的方法就是一个“引用传参”
          所以数组使用这种方法进行函数传参是可以修改数组元素的
      
          数组函数传参还有别的写法,但是这里只讲解这一种。
          希望你养成一个好习惯,数组传参务必带上数组大小
      */
      
      int main(){
          //声明初始化一个数组a
          int a[5]={1,2,3,4,5};
          //打印平均值
          printf("%lf\n",func(a,5));/
          return 0;
      }
      
      • 数组的本质(重点)

      • 数组本质是一个在内存中线性连续的存储空间

      • 你在声明数组的时候事实上获得的是一个这个空间的地址

      • 数组下标就是相对于数组地址的一个偏移量用来确定内部元素的位置

      • C语言中你要时刻注意数组下标,因为下标越界有时是不会报错的。

        你声明了一个

        1
        
        int a[10]={0}
        

        的数组,你可以使用如下的循环来遍历它

        1
        2
        3
        4
        5
        6
        7
        
        #include<stdio.h>
        int main(){
            int a[10]={0};
            for(int i=0;i<=10;i++)
                printf("%d ",a[i]);
            return 0;
        }
        

        样例输出

        1
        
        0 0 0 0 0 0 0 0 0 0 1
        
        • 我设置了循环条件是i<=10,但是你还记得吗?c语言数组下标从0开始!!!

        • 所以这里输出了11个数字。你也发现了,我明明初始化了所有的数组元素为0,但是最后却多输出了一个1。这里就是因为我们越界访问。

        • 最后这个数字1不属于数组的元素,但是你依旧可以访问它。

        • 所以每当你尝试访问数组的时候,请千万注意数组下标越界问题。因为不检查数组下标越界已经在计算机界造成了许许多多的重大安全问题。


  1. 字符数组

  • 字符数组本质还是数组,在大多数情况下和基本数组的操作方式类似。所以接下来我们只讲解字符串数组某些特殊操作。

  • 字符数组声明以及初始化

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    /*
    还记的\0吗,截止符
    字符数组内存储的字符串的末尾必须有截止符!
    */
    char name[6]={'h', 'e', 'l', 'l', 'o', '\0'};
    
    //你还可以用这个写法.
    char greeting[]="hello";
    //初始化部分元素也是可以滴
    char name[20]="xiaoming";
    
  • 字符数组的遍历

    因为字符串数组末尾必定有截止符的特性,我们有了一些常用的遍历字符数组的方法:

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    
    char str[]="hello world";
    for (int i=0;str[i]!=0;i++){
        ···
    }
    
    int i=0;
    while(str[i]!=0){
        ···
        i++;
    }
    

    当然你也可以获取字符串长度,然后设定下标i小于等于字符串长度的时候执行循环,不过那样就和普通数组没有区别音次不在此举例。

  • 字符串的输入输出

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    
    char str[100];
    //输入字符串
    gets(str);
    scanf("%s",&str);
    
    //输出字符串
    puts(str);
    printf("%s\n",str);
    
    /*
    之前说到在数组处理,越界并不会被报错
    所以这里的输入都存在着极大的安全隐患,于是在Visual studio的最新版本里,出于安全考虑
    在默认情况下不允许使用以上函数读取字符串。下面提到的C语言字符串处理函数也是一样,都存在了越界问题
    因而在最新的VS中默认不允许使用,这也是我不推荐使用最新版本的VS的原因
    */
    
  • C语言自带的字符串处理函数

    • 以下函数使用需引入头文件string.h(还记得怎么引入头文件吗?#include<string.h>)
    函数作用
    strcpy(s1,s2);复制字符串s2到字符串s1
    strcat(s1,s2);连接字符串s2到s1末尾
    strlen(s);获取字符串s长度
    strcmp(s1,s2);如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。(这里的s1 s2基于字符数组内的字符的字典序判定)
    strchr(s1,ch);返回一个指针,只想字符串s1中字符ch第一次出现的位置
    strstr(s1,s2);返回一个指针,指出字符串s1在字符串s2中年第一次出现的位置
  • 重点

  • sizeof()与strlen()函数对字符串的差别

  • sizeof()函数获取变量所占空间大小

  • strlen()获取字符串长度

    sizeof()函数是可以对任何变量使用的,但是strlen()是字符串专属函数

    1
    2
    3
    4
    5
    6
    7
    8
    
    #include<stdio.h>
    #include<string.h>
    int main(){
        char name[]="xiaoming";
        printf("size of name=%d\n",sizeof(name));
        printf("strlen of name=%d\n",strlen(name));
        return 0;
    }
    

    样例输出

    1
    2
    
    size of name=9
    strlen of name=8
    
    • strlen()函数统计字符数组内存储的字符串长度并且不包括截止符
    • sizeof()函数统计字符数组所占的空间大小!
  • 补充:

    1
    2
    3
    4
    5
    6
    7
    8
    
    #include<stdio.h>
    #include<string.h>
    int main(){
        char name[20]="xiaoming";
        printf("size of name=%d\n",sizeof(name));
        printf("strlen of name=%d\n",strlen(name));
        return 0;
    }
    
  • 样例输出

    1
    2
    
    size of name=20
    strlen of name=8
    

  1. 结构体

    • 有的时候你可能在想,我需要在程序里存储一个人,或者多个人的身份数据,包括姓名,年龄,出生年月一类的时候咋办?我们当然会希望把这些数据放在一起组织起来,而不是用N多个变量分别存储。于是!结构体就应运而生。

    • 我们直接以上面的例子来:情景假设我们要存储一个班级学生的信息。

       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      
      #include<stdio.h>
      int main(){
          //首先定义一个结构体DATE来存储出生年月
          struct DATE{
              short year; //年
              short month;//月
              short day;  //日
          };
      
          //定义学生信息结构体StudentInfo
          struct StudentInfo{
              char name[20]={0};  //姓名
              DATE date;  //出生年月,对!结构体也可以嵌套定义
          };
      
          //对,我们要存储一个班的学生,50人
          StudentInfo student[50];
      
          for (int i=0;i<50;i++){
              printf("输入第%d个学生信息:\n",i+1);
              //我们使用结构体名称加“.”的方式来访问结构体内部元素
              printf("学生姓名:"); scanf("%s",&student[i].name);
              printf("学生出生年月日:"); 
              scanf(
                  "%d-%d-%d",
                  &student[i].date.year,
                  &student[i].date.month,
                  &student[i].date.day
              );
          };
      
          for (int i=0;i<50;i++){
              printf("%s\n",student[i].name);
              printf(
                  "%d-%d-%d",
                  student[i].date.year,
                  student[i].date.month,
                  student[i].date.day
              );
          };
          return 0;
      }
      

      样例输入

      1
      2
      3
      
      输入第1个学生信息:
      学生姓名:xiaoming
      学生出生年月日:1992-12-21
      

      样例输出

      1
      2
      
      xiaoming
      1992-12-21
      
  • 我们通过如下格式声明结构体

    1
    2
    3
    4
    5
    
    struct 结构体类型名{
        基本数据类型 变量名;
        基本数据类型 变量名;
        ...
    }
    
    • 结构体的声明,其实只是定义了一个由用户个人定义的一种数据类型,我们真正要使用的时候还需要重新真正声明一个结构体实际变量.正如上面代码例子中给出的那样。
    • 除了上述的声明结构体实际变量的写法,我们还有如下几种其他方式
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    //如此我们在声明一个DATE结构体类型的同时,声明了一个date的结构体实体变量
    stuct DATE{
        int year;
        int month;
        int day;
    }date;
    
    //如果我们并不打算在别的地方使用这种结构体类型,只是单纯的为了声明这样一个结构体实体变量,还可以省略结构体类型名称
    struct{
        int year;
        int month;
    }date1; 
    
    //这个还是定义一个结构体类型DATE
    typedef stuct{
        int year;
        int month;
    }DATE;
    

  1. 枚举结构

    这是本垃圾博主很少使用的一种结构

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    enum WEEK{
        Monday=1,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    }
    
    WEEK day;
    /*
    你也可以使用这种写法多写一个单词
    enum WEEK day;
    
    当然也可以像结构体一样声明枚举类型,到实体化枚举变量一气呵成
    enum WEEK{
        Monday=1,
        Tuesday,
        Wednesday,
        Thursday,
        Friday,
        Saturday,
        Sunday
    } day;
    */
    
    • 第一个枚举类型成员值默认为整形0,后续枚举成员默认值为前一个成员+1.
    1
    
    enum season {spring, summer=3, autumn, winter};
    
    • 没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5

指针

  • 无数萌新惨死在这里

  • 指针是一种变量类型,这种类型可以存储另一个变量的地址,地址即另一个变量在内存中的位置

  • 你可以使用指针里存储的地址来访问另一个变量的值,修改另一个变量

  1. 声明方法

    1
    
    基础数据类型 *变量名
    
    • 举例1:
    1
    2
    3
    4
    5
    
    int *p0;
    short *p1;
    long *p2;
    char *p3;
    float *p4;
    
  • 基本的数据类型都可以声明其对应的指针变量

  • 一种类型的指针变量只可以存储对应类型变量的地址。列如int*型的指针变量,所存储的地址指向的位置必定存储的也是一个int型的数据

  • 举例2:

    1
    2
    3
    4
    
    struct TEST{
        int a,b;
    };
    TEST* p0;
    
  • 我们也可以声明一个结构体类型,并且声明一个指向该结构体类型的指针变量


  1. 指针使用

    • 基本使用
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    #include<stdio.h>
    int main(){
        int *p=NULL; //声明指针变量,并初始化为NULL(即0)NULL是C语言里约定的空指针值
        int a;  //声明一个正常整形变量
    
        a=1;    //将a赋值为1
        p=&a;  //将a的地址赋值给指针变量
        //& 符号,取变量的地址
    
        printf("a=%d\np=%p\n*p=%d",a,p,*p);
        a=22;   //改变a的值
        printf("\na=%d\np=%p\n*p=%d",a,p,*p);
        return 0;
    }
    
    • 样例输出
    1
    2
    3
    4
    5
    6
    7
    
    a=1                 //a的值
    p=000000000062FE44  //p的值,即a的地址
    *p=1                //p所指向的地址所存储的值,即a的值
    
    a=22
    p=000000000062FE44  //同上
    *p=22
    

文件操作

  • 程序是用了来处理数据的,而数据实质就是存储在磁盘上的文件,因而文件操作是程序员必须学习的基础
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
FILE *f=NULL;   //声明一个文件指针
char buff[100]; //声明一个字符串数组
char ch;        //声明一个字符变量
int x;          //声明一个整形变量

f=fopen("test.txt","wr");   //打开文件,“wr”指定可读可写
ch=fgetc(f);                //从文件中读取单个字符赋值给ch
fscanf(f,"%s",buff);        //从文件中读取字符串赋值给buff字符串数组
fscanf(f,"%d",&x);          //从文件中读取一个整形变量
fgets(buff,100,f);          //从文件中读取字符串,并存放到buff中,指定最大长度100
fprintf(f,"%s\n",buff);     //向文件中写入字符串数组

fputc(ch,f);                //向文件中写入单个字符
fputs(buff,f);              //向文件中写入字符串

后记

  • 指针我这里暂时咕了,指针可以说是c语言的精髓,也意味着贯穿了整个c语言的所有内容,如果完成指针部分可能又花当前篇幅的一半。所以这里只有一个最最最基础的指针使用。如果你还需要详细的指针学习,推荐你前往这里,或许之后我也可能会把指针单独独立的讲解一次。

  • 写着整个教程花了我接近三天的时间,而且越是到后面意志越是消沉(所以我写到指针咕咕咕了)

  • 而且我知道这样实在是一个写的不怎么样的教程。

  • 本教程尽量挑选了基础性的,使用较为常见的知识点进行讲解,并以本人自认为合适的一种顺序组织安排了全部内容

  • 如果这篇博客能帮到你,非常高兴。

  • 如果觉得博客有问题,欢迎邮件交流。

updatedupdated2025-04-162025-04-16