zoukankan      html  css  js  c++  java
  • [知识点] 1.1 C 语言基础

    总目录 > 1  语言基础 > 1.1  C 语言基础

    前言

    这篇文章对 C 语言里一些最基础的编程知识进行简要介绍。因为对 C / C++ 大部分入门内容都轻车熟路了,有些地方可能就不恰当地略过了。

    更新日志

    20200713 - 分支与循环部分施工完毕。

    20200716所有内容全部施工完毕。

    20200901 - 1.2 C++ 入门基础知识 现更改为 1.1 C 语言基础,仅介绍 C 语言中存在的基础知识。

    20200903 - 部分内容移动至 1.2 C 语言数据类型,介绍变量,常量,数组,结构体与函数等内容。

    子目录列表

    1、环境与编译

    2、头文件与主函数

    3、注释

    4、输入与输出

    5、格式与缩进

    6、预处理器

    7、运算符

    8、分支与循环

    9、文件操作

    1.1  C 语言基础

    1、环境与编译

    ① 概念

    众所周知,C / C++ 是一种计算机语言,是开发者和计算机的沟通方式,是两者之间的桥梁。同样众所周知的是,计算机本身并不能识别这些由英文单词和数字组成的话,而只能理解由 0 和 1 构成的二进制数串。那么,要将 C / C++ 翻译成二进制数,则需要一位翻译员进行翻译,在计算机领域里,翻译的过程被称作 —— 编译(compile),而担任翻译员的,则是编译器

    ② IDE

    请参见:施工中

    ③ 编译器

    Windows 下需要自行下载,当然 IDE 可能直接集成了编译器,或者提供了下载渠道。

    macOS 在终端执行:xcode-select --install

    Linux 在终端执行:sudo apt update && sudo apt install g++

    ④ 命令行执行

    如果不想被 IDE 的框架受限,或者想显得更高端的话,可以在命令行直接进行编译,格式为(C++):

    g++ *.cpp -o *,* 表示要编译的文件名,-o 用来指定输出文件名(C 格式类似,此处不介绍)

    除此之外,还有许多可以添加的编译选项,列举几个常用的:

    -O / -O1 优化生成代码;-O2 进一步优化;-O3 再进一步优化;

    -w 关闭所有警告提醒;-Wall 显示所有警告提醒;

    -lm 链接数学库(这个一般竞赛编译都会加上,但其实不加上也能正常运行)

    2、头文件与主函数

    先演示一个最经典的 C 语言示例代码。

    #include <cstdio>
     
    int main() {
        printf("Hello, world!"); // output 'Hello, world!'
        return 0;
    } 

    ① 头文件

    其中,第一行(L1)为头文件格式:

    #include <头文件名>

    头文件的作用是什么?一般,头文件包含了各类功能函数或者数据接口声明。任何函数都不是与生俱来的,而是保存在 C / C++ 里内置的各种 .h 文件中,如果不事先声明,编译器并不能识别这是什么。

    比如,L4 的 printf 函数是来自内置的 <cstdio> 中,故需要在最开始表明,接下来的代码需要用到这里面的函数,那么编译器在执行主程序之前就会先进行预处理。

    一般而言,只需要写上你需要的头文件即可,都有时候可能出现遗漏,而编译器并不会在所有这类情况下报错,所以你可以选择:

    ① 提前写好常用的头文件作为模板,每次编写时直接复制上去;

    ② 多检查几次;

    ③ 使用万能头文件(仅适用于 C++)

    #include <bits/stdc++.h>

    它包含了 C++ 绝大多数头文件。但是请注意,首先 C 语言不能使用,MSVC 编译器不支持;其次并不是所有情况下都允许使用,多加留意考试/比赛规则;最后,头文件声明越多,对编译时间影响越大,尽管一般不至于导致 TLE。

    头文件也可以自己编写,比如自行定义了 “bebe.h”,并在其中定义了一些函数,那么在声明头文件时可以这样写:

    #include "bebe.h"

    注意,自定义头文件使用 " " 而不是 < >。

    一般使用不到,尤其竞赛就不可能用到了,所以暂时不过多介绍。

    声明头文件本身是一种预处理语句。关于预处理,下面会有介绍。

    ② 主函数 main

    L3 定义了一个主函数:int main()。任何 .c / .cpp 文件有且仅有一个主函数,且为整型(int)函数。注意,请不要使用各类古老的教材教辅上写的 void main(),实际上这种写法早已被废弃,是不会被编译通过的。

    L6 有一句 “return 0”,表示主函数的返回值为 0,作为主函数的结束标识,尽管可以不写,但还是建议写上,这是规范,某些情况下不写会出现错误。

    主函数本身也是一个函数。关于函数,将会在 施工中 介绍。

    3、注释

    上面的示例代码中的 L5 中,后面有个 “//” 符号,表示注释,对程序编译无任何影响,一般用于让某段代码暂时不执行,或者解释程序,便于日后维护或者供他人阅读。

    两种注释的方法:

    行内注释,以 “//” 开头,后面所有内容全部为注释;

    举例:如上示代码。

    注释块,以 “/*” 开头,以 “*/” 结尾,可以跨行,中间内容全部为注释。

    举例:

    /*
    ---------------------
        Hello, world!
    ---------------------
    */

    4、输入与输出

    在 C 语言中,使用 scanf / printf 函数分别进行数据的输入与输出,一般格式为:

    scanf("A", B);

    printf("A", B);

    "A" 内填写字符或者格式说明符,B 填写变量。

    scanf("%d", &a);  // 表示输入一个变量 a
    scanf("%d %d", &a, &b);  // 表示输入两个变量 a, b
    printf("1");  // 表示输出一个数 1,等同于 printf("%d", 1);
    printf("%d
    %d", a, b);  // 表示输出两个变量 a, b,中间会换行

    格式看起来很复杂(确实比 C++ 的 cin / cout 流输入输出函数要复杂许多),进行一些解释:

    > %d 是什么?

    格式说明符的一种。使用 scanf / printf 时,需要先声明该变量的格式,下面是常用的格式说明符:

      %d - 十进制有符号整型变量,对应 int 类型等;

      %c - 字符变量,对应 char 类型;

      %f - 单精度浮点型变量,对应 float 类型;

      %lf - 双精度浮点型变量,对应 double 类型;

      %s - 字符串(字符数组)变量,对应 char[](字符数组);

      %lld - 长整型变量(Windows 下使用 %I64d),对应 long long 类型。

    在 '%' 与字母之间添加一个数字,表示设置输出域宽(即输出数据占用的字符个数)。这个数字小于数本身位数没有影响;大于本身位数将自行填充空格。

    举例:

    printf("%3d", 5);  // 将输出 “  5”

    在 '%' 与字母之间添加一个 '.' 和一个数字,表示设置浮点数精度(即小数保留位数),仅适用于浮点型变量,即 %f, %lf。

    举例:

    printf("%.2lf", 2.077);  // 将输出 “2.08”

    > 是什么?

    转义字符的一种。使用 scanf / printf 时,用于表示一些不能直接输入的字符,下面是常用的转义字符:

    - 换行符; - 制表符;

    " - 表示 ";\ - 表示 (因为这两个字符可能出现歧义)

    > scanf 中的 & 是什么?

    & 表示取址运算符,表示变量在内存中的地址。具体请参见:1.4.3  指针与引用 中的 指针的声明 部分。

    5、格式与缩进

    C / C++ 对书写格式和缩进并无任何规定,理论上只要语法正确,想怎么写怎么写。但语言是什么?是人类进行沟通表达和传递信息的工具,不要满足于计算机能理解你的行为,所以尽可能地保持代码的格式统一和结构清晰,能形成自己的代码风格更好。当年在 cj 搞 OI 那么久,早期风格变化大,后来慢慢定型了,基本上是师承 bebe 的,过了若干年再次看到他的代码的时候发现还是那么像(

    对于格式,大多是空格位置、换行位置、大括号位置等方面有一定差异。

    对于缩进,一般有四格缩进两格缩进的区别。

    见仁见智。

    以前我是不怎么打空格的,后来看 bebe 和 zed 的代码,逐渐被同化,成了一个空格王;同时比较喜欢压行,用逗号,还有各种能简短代码的方式。

    粘贴一份 KMP 算法(请参见:施工中)的代码体现一下代码风格。。(C++ 代码)

     1 int main() {
     2     cin >> a + 1 >> b + 1;
     3     la = strlen(a + 1), lb = strlen(b + 1);
     4     fail[0] = -1;
     5     for (int i = 1, x = -1; i <= lb; i++) {
     6         while (x >= 0 && b[x + 1] != b[i]) x = fail[x]; 
     7         fail[i] = ++x;
     8     }
     9     for (int i = 1, x = 0; i <= la; i++) {
    10         while (x >= 0 && b[x + 1] != a[i]) x = fail[x];
    11         if (++x == lb) cout << i - lb + 1, exit(0);
    12     }
    13     cout << "N/A";
    14     return 0; 
    15 }

    以前我更不喜欢换行,现在可能好点了(

    6、预处理器

    预处理器是在真正的编译开始之前由编译器调用的独立程序。C 语言预处理器中提供了一些预处理命令,如 #define, #else, #elif, #endif, #error, #if, #ifdef, #ifndef, #include, #pragma, #undef 等,下面选取其中一些常用预处理介绍。

    ① #include 头文件声明

    一般格式为 #include <>include "",前者适用于 C 内置的头文件,后者适用于自定义头文件,所有内置的头文件均可以在 C 语言编译器目录下找到。

    一般情况下,头文件声明均书写在每个文件的起始位置。

    ② #define 宏定义

    > #define

    #define 用于定义一个标识符常量或带参的宏,本质上是一种文本替换,格式为:

    #define A B

    可以使程序中所有独立出现的 A 全部视作 B,使用起来非常灵活,比如:

    #define N 2020
    printf("%d", N); // 将输出 2020

    还可以带参数,比如:

    #define printf("%d", &a) PRINT(a)
    PRINT(5); // 将输出 5

    也正是因为使用很灵活,有时容易出现一些不可预见的问题,比如:

    #define sum(A, B) A + B
    int c = 2 * sum(3, 4);

    看起来是先求 3 + 4 = 7 再 7 * 2 = 14,但实际上会被理解为:

    int c = 2 * 3 + 4;

    故最后结果为 10 而非 14。

    所以建议一般只在进行简单的替换时才使用 #define,比如:

    int a[20000], b[20000], c[20000], d[20000];

    输出这么多次 20000 又麻烦又不美观,这时可以:

    #define MAXN 20000

    > #undef

    #undef 用于删除由 #define 定义的宏,比如:

    #define N

    那么,N 就不再会被替换。

    > typedef

    提到 #define 就不得不提 typedef。它并不属于预处理语句,只是和 #define 功能很相近,从其全称 typedefine 便可知。

    typedef 用来为一个已有的数据类型定义一个别名,格式为:

    typedef 原类型名 新类型名;

    可以用于原类型名太长而需要简化的情况,比如:

    typedef long long ll;

    可以用于掩饰复合类型,比如:

    typedef int arr[100];
    arr a;

    这样可以更便捷地定义 int 类型的数组。

    还可以用于隐藏指针语法

    相比 #define 宏定义,typedef 功能较弱,但是更为规范,会进行正确性检查。

    ③ #ifdef, #endif 条件编译

    一般格式为:

    #ifdef 标识符

        语句组 1

    [#else

        语句组 2]

    #endif

    [] 中内容表示可以缺省。这段预处理表示如果已经用 #define 定义了某标识符,就编译语句组 1,否则编译语句组 2(如果没有 #else 则没有否则部分)。

    平时相当常用的条件编译是一个有意思的例子。在前面我们提到,对于 long long 这个数据类型,使用 scanf / printf 输入输出时,其格式说明符在 Windows 和 Linux 系统下是不同的,分别是 %I64d 和 %lld。因为一般个人计算机都是使用 Windows,但绝大多数评测系统都是 Linux 平台上搭建,所以在自己的电脑上测试过之后上交之前,又需要把说明符进行一些修改,麻烦而容易出错。这时候,我们使用如下条件编译:

    #ifdef WIN32
    #define lld "%I64d"
    #else
    #define lld "%lld"
    #endif

    问题完美解决。WIN32 / Linux 是内置的标识符,分别表示当前操作系统为 Windows 和 Linux。

    7、运算符

    ① 算术运算符

    + 正/加法;- 负/减法;* 乘法;/ 除法;% 取模。

    ② 位运算符

    ~ 非;& 与;| 或;^ 异或;<< 左移;>> 右移。

    关于位运算更详细的介绍,请参见:6.1  位运算与进位制

    ③ 赋值运算符

    = 赋值,前面还可以加上算术运算符位运算符形成复合赋值运算符,比如:

    i += 2,等价于 i = i + 2;

    i %= j,等价于 i = i & j。

    ④ 自加自减符

    ++ 自加:i++ 等价于 i = i + 1;

    -- 自减:i-- 等价于 i = i - 1;

    更多使用方法和意义请参见:<施工中>

    ⑤ 比较运算符

    < 小于;<= 小于等于;> 大于;>= 大于等于;== 等于;!= 不等于。

    注意,== 表示等于,而 = 表示赋值,初学者常见问题之一。

    ⑥ 逻辑运算符

    && 逻辑与;|| 逻辑或;! 逻辑非。

    注意和前面的位运算符区分开来:位运算符是返回与 / 或 /... 操作的值,而逻辑运算符只返回其值是否为 0,为 0 时返回 0,其余情况就返回 1,用于各类判断而非运算。

    ⑦ 分号与逗号

    ; 分号:表示一个表达式的结束;

    , 逗号:用于将若干个表达式分隔开,有时候等价于分号,但实际有区别,这里不赘述。

    ⑧ 成员访问运算符

    [ ] 下标符;. 对象成员;& 取地址 / 引用类型符;* 寻址 / 解引用符;-> 指针成员;

    > 关于优先级

    所有运算符都有运算的先后顺序,就像我们平常计算的时候知道乘除优先于加减,下面给出 C++ 大部分运算符的优先级总表:

    8、分支与循环

    ① 分支

    > if 和 if - else 语句

    if (条件) {
        主体1;
    }
    else {
        主体2; 
    } 

    众多主流语言中必学的第一条语句 —— if 语句,基本判断语句。最基本结构为 if (...) ...,和英文一样,表示“如果……则……”;还可以在语句后加上 else ...,表示“如果……则……否则……”。

    if - else 语句还可以嵌套使用,举个例子:

    if (a == 1)    
        b = 10;
    else if (a == 2)
        b = 100;
    else if (a == 3)
        b = 1000;
    else
        b = 0;

    > switch 语句

    switch (选择句) {
        case 标签1:
            主体1; break;
        case 标签2:
            主体2; break;
        ... 
        default:
            主体n; 
    }

    其含义为:当选择句的返回值为标签 1 时,执行主体 1 语句;为标签 2 时,执行主体 2 语句,等等;如果不等于任何标签,则执行 default 中的主体 n 语句。

    标签本身没有数量限制。

    其中,选择句必须是整数类型表达式,标签必须是整型常量,所以相比之下局限性比 if - else 大很多。

    举个例子:

    switch (a) {
        case 1:
            b = 10;
            break;
        case 2:
            b = 100;
            break;
        case 3:
            b = 1000;
            break;
    }

    这段代码和上面的 if-else 代码是等价的。

    注意每一个 case 后都写上了 break,从 switch 的功能来看是必要的,但是如果不加,编译是没有问题的,只是会出现不一一对应执行的情况,比如:

    在第二个 case 中不加 break,且选择句返回值为标签 2,则将会执行主体 2, 3, ... 的语句,直到出现 break。

    举个例子:

    switch (a) {
        case 1:
            b = 10;
        case 2:
            b = 100;
        case 3:
            b = 1000;
            break;
        }
        return 0;

    那么,其实 a 不管是等于 1 / 2 / 3,最后 b 的值均为 1000。

    ② 循环

    顾名思义,许多时候我们要反复做一件相同的事情,比如我们需要求出 2 ^ 20,写二十次 * 2 显然是不合理的,这时候需要使用循环语句

    > for 语句

    for (初始化; 判断条件; 更新) {
        循环体;
    }

    最基本的循环语句,使用最广泛,功能最强大。就以上述求 2 ^ 20 为例,使用 for 循环可以这样写:

    ans = 1;
    for (int i = 1; i <= 20; i++) {
        ans = ans * 2;
    }

    初始化定义一个变量 i 并赋值为 1,其作用为计数器,当 i <= 20,即计数器计数到 20 之前,执行花括号内包含的循环体语句 ans = ans * 2;再执行更新语句 i++ 以进行累加。故 for 循环语句的执行顺序为:初始化语句 -> 判断条件 -> 循环体语句 -> 更新语句

    上述是 for 循环最基本的使用方式,而其实其灵活性极高。初始化、判断条件、更新和循环体四大部分,理论上都可以进行省略 —— 省略初始化语句,即不进行初始化;省略判断条件,即判断条件永远为真,一般会在循环体内在一定条件下通过某种方式跳出循环,如果没有,则会进入死循环;省略更新语句,即不进行更新,一般在循环体内包含了更新内容,否则也会进入死循环;省略循环体没有意义,但同样可以编译。

    C 语言中不允许在初始化语句里定义变量,所以需要在循环语句前就进行定义。

    > while 语句

    while (判断条件) {
        循环体; 
    }

    不能初始化的循环语句(初始化只能在循环前完成)。如果将上述 if 的结构套用到 while 中,则相当于:

    初始化; 
    while (判断条件) {
        循环体;
        更新; 
    }

    不同的是,判断条件不能省略。如果需要永远为真,则需要填入 true(或 1)。

    > do - while 语句

    do {
        循环体;
    } while (判断条件);

    和 while 语句类似,唯一区别是 do - while 语句先执行循环体再进行判断。

    三种循环一般情况下都是互通的,根据实际需要和个人习惯进行选择即可。

    > break 和 continue

    循环语句中很关键的两个语句。有时我们的中断循环的判断条件并非那么明朗而需审时度势,break 和 continue 能够满足我们的需求。

    break 的作用是立即退出当前循环。比如我们需要 1 + 2 + ... 累加到刚好大于 100,则可以:

    for (int i = 1; ; i++) {
        ans += i; 
        if (ans > 100) break;
    }

    continue 的作用是跳过本轮循环,直接准备下一轮循环。对于 for 循环是执行更新语句,对于 whlie 循环则直接判断。比如我们需要 1 + 3 + 5 + ... + 21,可以这样写:

    for (int i = 1; i <= 21; i++) {
        if (i % 2 == 0) continue;
        ans += i;
    }

    9、文件操作

    不涉及文件操作的时候,程序的输入输出都是直接在终端中进行的,引入文件操作后,可以使我们的程序从指定文件中读入数据,再将数据输出到指定文件。

    ① freopen 函数

    一般格式:

    freopen(文件名, "模式", 流);

    部分模式列表:

    r - 只读;w - 只写;r+ - 读写,文件必须存在;w+ - 读写,会自动新建;

    a - 只写,不存在会自动新建,存在会直接将数据附加在文件末尾,保留 EOF 符;

    a+ - 同 a,但不保留 EOF 符。

    比如:

    freopen("data.in", "r", stdin);
    freopen("data.out", "w", stdout)

    关闭文件(可以不写):

    fclose(stdin);
    fclose(stdout);

    ② fopen 函数

    格式和 freopen 类似:

    fopen(文件名, "模式");

    区别在需要定义文件指针,比如:

    FILE *in, *out;
    in = fopen("data.in", "r");
    out = fopen("data.out", "w");

    与此同时,输入输出的函数需要进行修改,比如:

    fscanf(in, "%d", &a);
    fprintf(out, "%d", a);

    相对比较麻烦,平时用的不太多。

    ③ fstream 函数

    C++ 特有,名为 “文件输入输出流”,格式为:

    fstream 流名(文件名);

    比如:

    fstream file("data.txt");

    其中,fstream 可以更改为 ifstream 或 ofstream,表示只支持读 / 写。

    中间的读写函数从 cin / cout 改成 file 即可。

    关闭文件:

    流名.close();

  • 相关阅读:
    在Windows上搭建Git Server
    Windows环境下Zookeeper 的安装与配置
    错误: 找不到或无法加载主类 org.apache.zookeeper.server.quorum.QuorumPeerMain
    windows环境搭建dubbo服务
    gunicorn 使用
    jQuery 插件autocomplete 应用
    PHP str_replace() 函数详解
    jQuery的deferred对象详解
    Elasticsearch tp5使用
    MySQL explain详解
  • 原文地址:https://www.cnblogs.com/jinkun113/p/12755769.html
Copyright © 2011-2022 走看看