CB1-1-新手村(一)

C/C++道经第1卷 - 第1阶 - 新手村(一)


传送门:CB1-1-新手村(一)
传送门:CB1-1-新手村(二)
传送门:CB1-1-新手村(三)

心法:本章使用 C 项目进行练习

练习项目结构如下

|_ v1-1-basic-rookie

S01. 环境搭建

心法:C 语言源于贝尔实验室,由丹尼斯·里奇基于肯·汤普森的 B 语言开发,两人用它重写了 UNIX 系统,随系统发展,C 语言也不断完善。

C 语言需要通过编译器将源代码编译为目标文件,而现代 IDE 一般都集成了 C 语言的编译器,所以无需单独安装,常用的现代 IDE 如下:

Codeblocks

  • 轻量级 IDE,安装包小,占用资源少,支持插件拓展。
  • 界面简洁,清晰,易上手,且支持个性化定制。
  • 适合初学者和低配电脑。

Visual Studio

  • 功能强大,支持多种语言,支持代码提示,支持断点调试。
  • 资源占用比较高,界面稍显复杂,支持高度定制化。
  • 适合大型企业级项目和 Windows 平台开发。

CLion

  • 专注于 C/C++ 开发,支持代码提示,支持CMake 构建和热部署。
  • 界面简洁,采用符合现代开发习惯的交互设计,性能平衡性较好。
  • 适合专业 C/C++ 开发者和基于 CMake 的项目。

E01. 安装CLion工具

武技:在 Windows 平台上下载安装并配置 CLion 集成开发工具。

  1. 下载CLion2024.3 后,傻瓜式安装即可,注意路径中不要有中文或特殊符号。
  2. 自行购买或激活 CLion,后续可通过 Help → About 查看激活情况。
  3. 使用管理员方式启动 @/bin/CLion64.exe 程序。

1. 禁用自动更新

武技:禁用 CLion 自动更新。

进入 File → Settings → Appearance & Behavior → System Settings → Updates 界面:

  • 取勾 Check IDE updates for … 选项。
  • 取勾 Check for plugin updates 选项。
  • 取勾 Show What’s New in the editor after an IDE update 选项。

在这里插入图片描述

2. 设置字体大小

武技:设置调整 CLion 字体大小的快捷键。

进入 File → Keymap 界面,然后展开 Editor Actions 栏,在右上角搜索栏中输入 font 关键字:

  • 选择 Increase Font Size,设置快捷键为 Ctrl + Wheel up,即 Ctrl 加鼠标上滑轮。
  • 选择 Decrease Font Size,设置快捷键为 Ctrl + Wheel down,即 Ctrl 加鼠标下滑轮。
  • 选择 Reset Font Size,设置快捷键为 Ctrl + Middle-Click,即 Ctrl 加鼠标中键。

在这里插入图片描述

3. 设置字符编码

武技:设置 CLion 字符编码(需要再次设置其他项目)。

进入 File → Settings → Editor → File Encodings 界面:

  • 修改 Global Encoding 为 UTF-8 编码。
  • 修改 Project Encoding 为 UTF-8 编码。
  • 修改 Default encoding for properties files 为 UTF-8 编码。

在这里插入图片描述

4. 解决中文乱码

武技:在 CLion 里,run.processes.with.pty 这个选项涉及到运行程序时是否使用伪终端(Pseudo-Terminal,PTY),不使用 PTY 时可以解决控制台的中文乱码问题。

使用快捷键 shift + ctrl + alt + / 打开 CLion 配置面板,然后选择 Registry 进入 CLion 注册表,然后取勾 run.processes.with.pty 项即可:

在这里插入图片描述

5. 修改调试窗口

心法:调整 CLion 为在外部控制台运行的模式,可以支持更多特定的终端命令,兼容性和稳定性更高,用户的交互体验也会更好。

  1. 运行 HelloWorld 程序,然后在 CLion 内置的控制台处找到三个竖点的图片,选择 Modify Run Configuration… 打开页面:

在这里插入图片描述

  1. 勾选 Run in external console 配置:

在这里插入图片描述

  1. 临时设置控制台编码为 UTF8,并使用 system(“pause”) 代码暂停住命令窗口以防止闪退:

在 C 源代码中,需要引入 #include <stdlib.h> 以启用 system(“”) 函数:

#include <stdio.h>
#include <stdlib.h>

int main(void) {
    // 临时设置控制台编码为UTF-8
    system("chcp 65001 > nul");
    // 输出语句
    printf("Hello, world!");
    // 暂停(要引入 #include <stdlib.h> 以启用 system() 函数)
    system("pause");
    return 0;
}

在 C++ 源代码中,需要引入 #include <iostream> 以启用 system(“”) 函数:

#include <iostream>

int main() {
    // 临时设置控制台编码为UTF-8
    system("chcp 65001 > nul");
    // 输出语句
    std::cout << "Hello, World!" << std::endl;
    // 暂停(要引入 #include <iostream> 以启用 system() 函数)
    system("pause");
    return 0;
}

E02. 开发C项目

1. 创建C项目

武技:使用 CLion 创建 C 项目 v1-1-basic-rookie

点击 New Project ,然后选择 C Executable 以创建 C 语言项目:

  1. location 栏填写项目地址,建议多指定一层目录,目录不存在会自动创建。
  2. Language standard 栏选择 C11 以配置 C 语言版本。
  3. 点击 Create 完成项目创建。

在这里插入图片描述

2. 开发主源文件

心法:在 C 语言里,main.c 通常是程序的主源文件,main 函数是程序的入口点。

include命令:在 C 和 C++ 标准中,使用 #include xxx 命令来引入程序所需的头文件,比如主文件中引入的 stdio.h 头文件,就是系统提供的标准输入输出库。

主函数返回值:在 C 和 C++ 标准中,主函数通常要求返回 int 整数,如果主函数中没有编写 return 语句,则隐式返回 0,表示程序正常结束,其他非零值表示程序异常结束。

主函数参数:若省略参数中的 void 表示该方法允许接收任意数量和类型的命令行参数,而使用 void 表示不接收任何参数,明确性更高,可读性更高。

心法:在主文件中开发 HelloWorld 代码。

  1. 开发 HelloWorld 代码:
// 引入IO头文件
#include <stdio.h>

/**
 * 主函数(参数 void 可以省略)
 * @return 主函数返回值固定为 int
 */
int main(void) {

    // 输出语句
    printf("Hello, C!");
    
    // 主函数的返回值可以省略
    return 0;
}
  1. 编译 HelloWorld 代码:CLion 在运行代码之前,会自动帮我们进行编译工作,无需手动操作。
  2. 运行 HelloWorld 代码:点击主方法左侧的播放按钮,使用 Debug 的方式运行该方法,在控制台查看结果。

3. 用户文件

心法:在 C 语言中,用户能够自主创建自定义的源文件与头文件(一般都会成对儿创建),不过这些自行创建的文件无法直接运行,仍需要在主源文件里对其进行调用,经过编译、链接等一系列操作后,程序才能顺利执行。

头文件(.h):主要用于存放声明信息,如宏定义,变量,函数,结构体,类等,方便多个源文件共享这些声明,当需要修改某个函数的声明时,只需修改头文件即可,类似于 Java 中的接口。

源文件(.c):主要用于存放头文件声明对应的实现,类似于 Java 中的实现类。

武技:测试用户文件

  1. 开发头文件:
// test/Test.h
// Created by JoeZhou on 2025/3/3.
//

#ifndef V1_1_BASIC_ROOKIE_TEST_H
#define V1_1_BASIC_ROOKIE_TEST_H

#include <stdio.h>

/** 函数声明 */
void test();

#endif //V1_1_BASIC_ROOKIE_TEST_H
  1. 开发源文件:
// test/Test.c
// Created by JoeZhou on 2025/3/3.
//

#include "Test.h"

/** 函数实现 */
void test() {
    printf("test");
}
  1. 测试头文件:
// main.c

#include <stdio.h>
#include "test/Test.h"

int main() {
    test();
}

E03. 开发CPP项目

1. 创建CPP项目

武技:使用 CLion 创建 C++ 项目 v1-1-basic-rookie

点击 New Project ,然后选择 C++ Executable 以创建 C++ 语言项目:

  1. location 栏填写项目地址,建议多指定一层目录,目录不存在会自动创建。
  2. Language standard 栏选择 C++20 以配置 C++ 语言版本。
  3. 点击 Create 完成项目创建。

在这里插入图片描述

2. 主源文件

心法:在 C 语言里,main.cpp 通常是程序的主源文件,main 函数是程序的入口点。

include命令:在 C 和 C++ 标准中,使用 #include xxx 命令来引入程序所需的头文件,比如主文件中引入的 iostream 头文件,就是系统提供的标准输入输出库,对比 C 语言的不同,C++ 在引入头文件时省略了 .h 后缀。

主函数返回值:在 C 和 C++ 标准中,主函数通常要求返回 int 整数,如果主函数中没有编写 return 语句,则隐式返回 0,表示程序正常结束,其他非零值表示程序异常结束。

心法:在主文件中开发 HelloWorld 代码

  1. 开发 HelloWorld 代码:
// main.cpp

# 引入IO头文件
#include <iostream>

/**
 * 主函数
 * @return 主函数返回值固定为 int
 */
int main() {

    // 输出语句
	std::cout << "Hello, World!" << std::endl;
    
    // 主函数的返回值可以省略
    return 0;
}
  1. 编译 HelloWorld 代码:CLion 在运行代码之前,会自动帮我们进行编译工作,无需手动操作。
  2. 运行 HelloWorld 代码:点击主方法左侧的播放按钮,使用 Debug 的方式运行该方法,在控制台查看结果。

3. 用户文件

心法:在 C++ 语言中,用户能够自主创建自定义的源文件与头文件(一般都会成对儿创建),不过这些自行创建的文件无法直接运行,仍需要在主源文件里对其进行调用,经过编译、链接等一系列操作后,程序才能顺利执行。

头文件(.hpp):主要用于存放声明信息,如宏定义,变量,函数,结构体,类等,方便多个源文件共享这些声明,当需要修改某个函数的声明时,只需修改头文件即可,类似于 Java 中的接口。

源文件(.cpp):主要用于存放头文件声明对应的实现,类似于 Java 中的实现类。

武技:测试用户文件

// test/Test.hpp
// Created by JoeZhou on 2025/3/3.
//

#ifndef V1_1_BASIC_ROOKIE_TEST_HPP
#define V1_1_BASIC_ROOKIE_TEST_HPP

#include <iostream>

/** 函数声明 */
void test();

#endif //V1_1_BASIC_ROOKIE_TEST_HPP
// test/Test.cpp
// Created by JoeZhou on 2025/3/3.
//

#include "Test.hpp"

/** 函数实现 */
void test() {
    std::cout << "Hello, World!" << std::endl;
}
// main.cpp

#include <iostream>
#include "test/Test.hpp"

int main() {
    test();
    return 0;
}

4. 命名空间

心法:在大型项目中,不同的库或者不同开发者编写的代码可能会使用相同的标识符(如变量名、函数名、类名等),这就会导致命名冲突,而 C++ 中提供了命名空间,专门用于解决命名冲突问题。

命名空间允许程序员将代码组织成不同的逻辑单元,每个单元有自己独立的命名空间,在不同命名空间中可以使用相同的名称而不会产生冲突。

定义命名空间:使用 namespace 关键字来定义一个命名空间,命名空间内可以包含变量、函数、类等各种元素:

  • 不要在命名空间中对 全局变量或函数(全局常量和内联函数除外) 直接进行初始化,否则当多个源文件同时引入该头文件时,会出现重复定义的错误,可以对全局变量或函数进行 extern 修饰(函数可以省略),表示在外部文件,如对应的源文件中再进行初始化即可。

访问命名空间:通过 命名空间名::元素名 的方式来访问命名空间中的元素。

using 声明:使用 using 声明可以在局部作用域内引入命名空间中的某个特定元素,之后可以直接使用该元素而无需再加上命名空间前缀。

// test/NameSpace.h
// Created by JoeZhou on 2025/3/3.
//

#ifndef V1_1_BASIC_ROOKIE_NAMESPACE_H
#define V1_1_BASIC_ROOKIE_NAMESPACE_H

#include <iostream>

// 定义一个名为 ns1 的命名空间
namespace ns1 {
    // 全局变量声明
    extern int a;
    // 函数声明
    extern void test();
}

// 定义一个名为 ns2 的命名空间
namespace ns2 {
    // 全局变量声明
    extern int a;
    // 函数声明
    extern void test();
}

#endif //V1_1_BASIC_ROOKIE_NAMESPACE_H
// test/NameSpace.cpp
// Created by JoeZhou on 2025/3/3.
//

#include "NameSpace.h"

int ns1::a = 1;
int ns2::a = 2;

void ns1::test() {
    std::cout << "ns1::test().a = " << a << std::endl;
}

void ns2::test() {
    std::cout << "ns2::test().a = " << a << std::endl;
}
// main.cpp
#include <iostream>
#include "test/NameSpace.h"

int main() {
    ns1::test();
    ns2::test();
    return 0;
}

E04. 可执行文件

心法:C/C++ 语言需要通过编译器将源代码编译为目标文件,C/C++ 源代码成功编译后,会在项目的 cmake-build-debug 目录中生成一个和项目同名的 exe 文件,该文件就是 Windows 平台中的可执行文件。

在这里插入图片描述

武技:测试可执行文件

可将 .exe 文件拖到 CMD 运行,或双击(但双击后会立即关闭,不建议)。

# 查看当前终端编码:936表示GBK编码,65001表示UTF-8编码
chcp

# 将终端临时切换为UTF-8编码
chcp 65001

# 运行可执行文件
v1_1_basic_rookie.exe

S02. 基础入门

E01. 预处理机制

心法:程序运行前,C 预处理器会根据预处理指令,将指定内容进行替换。

1. include 指令

心法:预处理器遇到 include 指令时,会将指定文件内容插入当前文件,替换该指令,比如主页面自带了一行 #include <stdio.h> 代码,用于将系统库中的 stdio.h 头文件引入进来并替换。

指令格式

// 用于包含标准库头文件
// 编译器会在系统指定的标准库路径中查找这些文件
#include <文件>

// 用于包含用户自定义的头文件
// 编译器首先会在当前源文件所在的目录中查找该文件
// 如果找不到,再到系统指定的其他路径中查找
#include "文件"

武技:测试 include 指令

  1. 开发 C/C++ Header File 头文件,里面自带的内容可以全部删除:
// init/HelloWorld.h

void testHelloWorld();
  1. 开发 C/C++ Source File 源文件,建议和头文件同名,方便对应:
// init/HelloWorld.c
// Created by 周航宇 on 2025/2/26.
//
#include <stdio.h>
#include "HelloWorld.h"

void testHelloWorld() {
    printf("Hello World!\n");
}
  1. 在 CMakeLists.txt 文件中确认加入了自定义的 C 源文件(没加入自己补),头文件可以不用加入:

注意:通过右键 new 出来的源文件会自动加入到 CMakeLists.txt 的 add_executable() 中,而通过复制粘贴出来的源文件不会,需要手动加入到 CMakeLists.txt 的 add_executable() 中:

在这里插入图片描述

  1. 在主页面中引入指定的头文件,然后运行查看结果:
// main.c
// Created by 周航宇 on 2025/2/26.
//

#include "00_init/HelloWorld.h"

int main() {
    testHelloWorld();
}

2. define 指令

心法:预处理器遇到 define 指令时,会将参数批量替换到文本中,该操作通常被称为 定义宏 ,该替换操作发生在程序运行之前,属于纯文本替换(字符串中的文本不会被替换)。

指令格式

// 注意定义宏的时候末尾不能加分号结束
#define 宏名 被替换的内容

武技:测试 define 指令

// init/Define.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>

// 将文件中全部 PI 单词替换为 3.14
#define PI 3.14

// 将文件中全部 myDouble 单词替换为 double
#define myDouble double

// 将文件中全部 mySquare(r) 单词替换为 r * r,其中 r 是一个变量,括号为了保证优先级
#define mySquare(r) ((r) * (r))

// 将文件中全部 myLog(msg) 单词替换为 printf("日志:%s\n", #msg)
// msg 是一个变量
// #msg 会将 str 转换为字符串,例如:#123 会转换为 "123",#abc 会转换为 "abc"
#define myLog(msg) printf("日志:%s\n", #msg)

// 将文件中全部 myVar(n) 单词替换为 x##n,其中 n 是一个变量,## 会使用参数进行拼接
// 例如:myVar(1) 会替换为 a1,myVar(2) 会替换为 a2
#define myVar(n) a##n

void testDefine() {
    printf("PI = %.2f\n", PI);
    myDouble radius = 2.0;
    printf("半径 = %.2lf\n", radius);
    double area = PI * mySquare(radius);
    printf("面积 = %.2lf\n", area);
    myLog("调用结束");
    int myVar(1) = 10;
    printf("a1 = %d\n", a1);
}

3. undef指令

心法:undef 指令用于取消之前使用 define 定义的宏,当编译器遇到 undef 指令后,后续代码将不再识别该宏。

在 C 语言中,宏是定义常量和简单函数的常用手段,因此 undef 指令 的使用相对较为常见。例如,在一些大型项目中,可能会根据不同的编译选项定义不同的宏,当某个宏的作用范围结束后,就可以使用 undef 指令取消其定义,避免对后续代码产生影响。

指令格式

// 注意取消宏的时候末尾不能加分号结束
#undef 宏名

示例如下

// init/Define.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>
#define myInt int
#undef myInt

void testUnDef() {
    // myInt a = 10;
    // printf("a = %d\n", a);
}

4. 预定义宏

心法:C 语言预定义宏由编译器预设,提供编译环境、代码位置和日期等信息。

预定义宏名称含义
__STDC_VERSION__编译器支持的 C 标准版本
例如 199901L 表示支持 C99 标准,201112L 表示支持 C11 标准
__func__当前函数的名称
__DATE__当前的日期,格式为类似 Jun 27 2023 的字符串
__TIME__当前的时间,格式为类似 10:23:12 的字符串
__FILE__当前源代码文件的名称(含路径)的字符串
__LINE__当前所处的行号是多少就替换为多少,整数

武技:测试预定义宏

// init/Define.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>

void sysDefine() {
    // 201112L 表示 C11 标准
    printf("__STDC_VERSION__ = %ld\n", __STDC_VERSION__);
	printf("__func__ = %s\n", __func__);
    printf("__FILE__ = %s\n", __FILE__);
    printf("__FILE_NAME__ = %s\n", __FILE_NAME__);
    printf("__LINE__ = %d\n", __LINE__);
    printf("__DATE__ = %s\n", __DATE__);
    printf("__TIME__ = %s\n", __TIME__);
}

5. 条件编译

心法:条件编译包含 ifdef, else, endif 等编译指令,且支持嵌套。

重复包含问题:在大型项目中,一个源文件可能会多次包含同一个头文件,或者多个源文件都会包含同一个头文件,若头文件里有全局的变量、函数声明、结构体定义等内容,重复包含头文件就可能导致编译错误,像重复定义之类的问题。

头文件卫士:为了避免重复包含问题,C 语言通常会使用预处理器指令来创建头文件保护(也叫头文件卫士,Header Guards),使用方式如下:

#ifndef HEADER_FILE_NAME_H
#define HEADER_FILE_NAME_H

// 头文件内容,比如函数声明、结构体定义等

#endif // HEADER_FILE_NAME_H

指令格式

示例一:如果 a 已经被 define 定义,则执行内容 A,否则执行内容 B:

#ifdef  a     内容A
#else         内容B
#endif

示例二:如果 a 没有被 define 定义,则执行内容 A,否则执行内容 B:

#ifndef a     内容A
#else         内容B
#endif

示例三:如果 a 大于 60,则执行内容 A,如果 a 小于 60,则执行内容 B,如果 a 等于 60 ,则执行内容 C,均不满足时执行内容 D:

#if   a >  60 内容A
#elif a <  60 内容B
#elif a == 60 内容C
#else         内容D
#endif

武技:测试常用的条件编译指令

// init/Define.c
// Created by 周航宇 on 2025/2/26.
//

#define monkey "SWK"
#ifdef monkey
#define monkeyResult "monkey被定义了"
#else
#define monkeyResult "monkey没被定义"
#endif

void testIfDef() {
    printf("%s", monkeyResult);
}

#define myScore 59
#if myScore > 90
	#define myScoreResult "A"
#elif myScore > 80
	#define myScoreResult "B"
#elif myScore > 70
	#define myScoreResult "C"
#elif myScore > 60
	#define myScoreResult "D"
#else
	#define myScoreResult "E"
#endif

void testIfElifElse() {
    printf("%s", myScoreResult);
}

E02. 程序运行原理

心法:C 语言运行一共分为预处理,编译,汇编,链接,加载 5 个步骤。

在这里插入图片描述

预处理器:负责处理以 # 开头的预处理指令,比如文本替换,条件编译和删除注释等工作,实现了代码模块化(头文件),常量定义和条件编译,提升了系统的维护性和移植性。

编译器:负责将预处理后的源代码转为目标代码或中间汇编代码,检查语法并优化代码,编译器将高级语言转为机器码,让程序员用抽象语言编程,无需直接用机器语言。

汇编器:汇编器负责将汇编语言转为二进制指令,并生成一个二进制目标文件,汇编器连接了高级语言与机器语言,将汇编代码转为计算机可执行的机器指令。

链接器:负责将这个二进制目标文件与系统库等进行链接,最终生成平台可执行文件:

  • 链接器用于将多个目标文件以及库文件组合成一个可执行文件。
  • 链接器使得大型程序可以被分解为多个模块进行开发,便于开发和管理。

加载器:负责将可执行文件载入内存,分配空间,读取代码和数据,设好程序计数器,从入口点开始执行,加载器将程序从磁盘加载到内存,确保其在计算机中正确运行。

1. GCC

心法:GCC 是一个强大的编译器集合,主要用于将高级编程语言(如 C、C++ 等)编译成机器语言。

GCC 原名 GNU C Compiler,只能处理 C 语言,现名 GNU Compiler Collection,拓展支持了 Fortran,Objective -C、Java、Go,汇编等多种语言。

武技:使用 GCC 编译器工具手动编译源代码

  1. 把 CLion 自带的 GCC 工具的 bin 目录配置到环境变量 path 中,然后重启 CLion 使其生效:
# 配置到 path 中,然后重启 CLion
D:\clion\CLion-2024.3.win\bin\mingw\bin
  1. 使用 cmd 命令行手动编译源文件:
# 切换到项目目录
cd D:\workspace\c\v1-1-basic-rookie

# 预处理,得到 .i 源文件
gcc -E main.c -o main.i

# 编译,得到 .s 汇编文件
gcc -S main.i -o main.s

# 转换,得到 .o 二进制文件
gcc -c main.s -o main.o

# 链接,得到 .exe 可执行文件,.exe 后缀可以省略
gcc main.o -o main.exe

# 以上四步可以简化为1步:多个 .c 源文件直接空格隔开即可
gcc main.c -o main

# 加载,直接运行可执行文件
./main.exe

2. Make

心法:Make 是 1977 年推出的一个构建工具,用于通过 Makefile 文件中定义的依赖关系来自动化构建项目。

GCC 擅长编译源文件,但不擅长管理项目构建和依赖,而 Make 通过依赖管理和增量构建,高效处理大型项目的构建,特别适合多源文件和复杂依赖关系。

Makefile 编写格式如下

  • 注意:Makefile 中的命令必须以 tab 键开头,不能使用空格。
  • 注意:Makefile 中的命令必须以换行符结尾,不能使用分号。
# targets:最终构建的目标,名称自定义,如 HelloWorld.exe
# depends:构建依赖文件,多个依赖文件用空格分割,如 hello.o world.o
# command:构建命令,如 `gcc hello.o world.o -o HelloWorld.exe`

targets: depends
	command

targets: depends
	command
...

武技:用 Make 工具编译 main.c 和 00_init/HelloWorld.c,并合并生成 main.exe 可执行文件。

  1. 在根目录下编写 Makefile 文件:
# 根据 main.o 和 helloWorld.o 构建 main.exe
main.exe: main.o HelloWorld.o
	gcc main.o 00_init/HelloWorld.o -o main.exe

# 根据 main.c 构建 main.o
main.o: main.c
	gcc -c main.c -o main.o

# 根据 00_init/HelloWorld.c 构建 00_init/HelloWorld.o
HelloWorld.o: 00_init/HelloWorld.c
	gcc -c 00_init/HelloWorld.c -o 00_init/HelloWorld.o
  1. 在控制台编译:
    • 如果没修改源文件,则本次编译不会做任何事情,提示 “xxx is up to date” 信息。
    • 如果修改了源文件,则本次编译都会重新构建目标文件。
# 编译
mingw32-make.exe

# 运行
main.exe

3. CMake

心法:CMake 是一个跨平台的构建工具,用于通过 CMakeLists.txt 文件中定义的依赖关系来自动化构建项目,并且可以根据这些信息为不同的平台生成相应的构建文件。

CLion 默认使用 CMake 构建工具,当我们创建一个项目后,CLion 会自动为我们配置 CMake,而具体的配置都是写在 CMakeList.txt 文件中的。

CMakeList.txt 内容如下

# 指定当前CMake的最低版本,程序低于此版本时无法构建
cmake_minimum_required(VERSION 3.30)
# 项目名和语言,名称随意,语言默认为C
project(v1_1_basic_rookie C)
# 设定环境变量,如设定C11标准
set(CMAKE_C_STANDARD 11)
# 添加可执行文件,参数包括一个可执行文件名,以及 N 个需要编译的源文件(头文件可以不写)
add_executable(v1_1_basic_rookie main.c 00_init/HelloWorld.c)

武技:测试 CMake 工具

  1. 把 CLion 自带的 CMake 工具的 bin 目录(如 D:\clion\CLion-2024.3.win\bin\cmake\win\x64\bin)配置到环境变量 path 中。
  2. 重启 CLion 使其生效。
  3. 使用 CMake 工具生成 MakeFile 文件:
# 切换到项目根目录
cd D:\workspace\c\v1-1-basic-rookie

# -S 源文件目录 -B 构建目录 -G 生成器
cmake -S . -B cmake-test -G "MinGW Makefiles"

# 切换到构建目录中
cd cmake-test

# 执行 make 编译
mingw32-make.exe

# 运行
v1_1_basic_rookie.exe

E03. 代码注释

心法:注释是对代码的一种文字说明,提高可读性,不参与编译过程,尽量少写废话并规避行尾注释。

单行注释 //:写在方法函数内,独占 1 行,C99 及以上版本支持。

多行注释 /* 内容 */:写在方法函数内,独占 1 或 N 行,C98 及以上版本支持。

文档注释 /** 内容 */ :一般写在方法函数外,独占 1 或 N 行,额外支持 @param,@return 等注解。

TODO 注释:任何注释中都可以使用 TODO 前缀来标记一个待做事项,后续可通过 IDEA 的 TODO 窗口来查看。

武技:测试注释格式

// basic/Comment.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>

void testSingleLine(){
    // 我是单行注释
    printf("单行注释测试!\n");
}

void testMultiLine(){
    /* 我是多行注释 */
    printf("多行注释测试!\n");
}

/**
 * 两个数的求和函数
 * @param a 第一操作数
 * @param b 第二操作数
 * @return 两个操作数的和
 */
int testDoc(int a, int b){
    return a + b;
}

E04. 输出语句

心法:输出语句用于将指定的内容显示在控制台上,为了提高输出效率,减少对屏幕的频繁访问,输出语句会使用缓冲机制,这意味着输出的数据不会立即显示在屏幕上,而是先存储在缓冲区中,当缓冲区满或者遇到手动刷新缓冲区的代码时,缓冲区中的数据才会被输出到屏幕上。

相关 API 方法描述
fflush(stdout)手动刷新缓冲区,此时缓冲区中的数据会立刻输出到屏幕上
sleep(n)程序睡眠 n 秒,需要引入 unistd.h 头文件(Unix 标准系统调用头文件)

1. printf

心法:格式化输出格式为 printf(“模板”, 不定长参数),需要导入 <stdio.h> 头文件。

格式化输出不换行且不允许空输出:

  • 如果输出的变量值的字节长度小于占位符类型的长度,则会在二进制位高位补零。
  • 如果输出的变量值的字节长度大于占位符类型的长度,则会在二进制位高位截取。

常用的模板占位符如下

模板占位符描述示例
%hd十进制且有符号的 short 型整数short a = 10
%d十进制且有符号的 int 型整数int a = 10
%ld十进制且有符号的 long 型整数long a = 10
%lld十进制且有符号的 long long 型整数long long a = 10
%hu十进制且无符号的 short 型整数unsinged short a = 10
%u十进制且无符号的 int 型整数unsinged int a = 10
%lu十进制且无符号的 long 型整数unsinged long a = 10
%llu十进制且无符号的 long long 型整数unsinged long long a = 10
%ho八进制的 short 型整数short a = 0123
%o八进制的 int 型整数int a = 0123
%lo八进制的 long 型整数long a = 0123
%llo八进制的 long long 型整数long long a = 0123
%hx十六进制的 short 型整数,小写 x 符号short a = 0x123
%x十六进制的 int 型整数,小写 x 符号int a = 0x123
%lx十六进制的 long 型整数,小写 x 符号long a = 0x123
%llx十六进制的 long long 型整数,小写 x 符号long long a = 0x123
%hX十六进制的 short 型整数,大写 X 符号short a = 0X123
%X十六进制的 int 型整数,大写 X 符号int a = 0X123
%lX十六进制的 long 型整数,大写 X 符号long a = 0X123
%llX十六进制的 long long 型整数,大写 X 符号long long a = 0X123
%ffloat 型浮点数,%.nf 表示保留 n 位小数,默认 n=6float f = 3.15F
%lfdouble 型浮点数,%.nf 表示保留 n 位小数,默认 n=6double f = 3.15
%c单一字符,仅支持存储 ASCII 码表中的字符,不支持存储中文char a = 'A'
%s字符串char a[] = "hello"
%%百分号
%p指针int arr[] = {1}

ASCII 表 - 控制字符:ASCII 表中,0~31 表示控制字符,一般无法直接显示,详情如下:

十进制十六进制二进制字符/缩写解释
00x0000000000NUL (NULL)空字符
10x0100000001SOH (Start Of Headling)标题开始
20x0200000010STX (Start Of Text)正文开始
30x0300000011ETX (End Of Text)正文结束
40x0400000100EOT (End Of Transmission)传输结束
50x0500000101ENQ (Enquiry)请求
60x0600000110ACK (Acknowledge)回应/响应/收到通知
70x0700000111BEL (Bell)响铃
80x0800001000BS (Backspace)退格
90x0900001001HT (Horizontal Tab)水平制表符
100x0A00001010LF/NL(Line Feed/New Line)换行键
110x0B00001011VT (Vertical Tab)垂直制表符
120x0C00001100FF/NP (Form Feed/New Page)换页键
130x0D00001101CR (Carriage Return)回车键
140x0E00001110SO (Shift Out)不用切换
150x0F00001111SI (Shift In)启用切换
160x1000010000DLE (Data Link Escape)数据链路转义
170x1100010001DC1/XON (Device Control 1/Transmission On)设备控制1/传输开始
180x1200010010DC2 (Device Control 2)设备控制2
190x1300010011DC3/XOFF (Device Control 3/Transmission Off)设备控制3/传输中断
200x1400010100DC4 (Device Control 4)设备控制4
210x1500010101NAK (Negative Acknowledge)无响应/非正常响应/拒绝接收
220x1600010110SYN (Synchronous Idle)同步空闲
230x1700010111ETB (End of Transmission Block)传输块结束/块传输终止
240x1800011000CAN (Cancel)取消
250x1900011001EM (End of Medium)已到介质末端/介质存储已满/介质中断
260x1A00011010SUB (Substitute)替补/替换
270x1B00011011ESC (Escape)逃离/取消
280x1C00011100FS (File Separator)文件分割符
290x1D00011101GS (Group Separator)组分隔符/分组符
300x1E00011110RS (Record Separator)记录分离符
310x1F00011111US (Unit Separator)单元分隔符

ASCII 表 - 键盘字符:ASCII 表中,32~126 表示键盘字符,能在键盘上直接找到,详情如下:

十进制十六进制二进制字符/缩写解释
320x2000100000(space)空格
330x2100100001!感叹号
340x2200100010"双引号
350x2300100011#井号
360x2400100100$美元符号
370x2500100101%百分号
380x2600100110&和号
390x2700100111单引号
400x2800101000(左括号
410x2900101001)右括号
420x2A00101010*星号
430x2B00101011+加号
440x2C00101100,逗号
450x2D00101101-减号
460x2E00101110.句号
470x2F00101111/斜杠
480x30001100000数字0
490x31001100011数字1
500x32001100102数字2
510x33001100113数字3
520x34001101004数字4
530x35001101015数字5
540x36001101106数字6
550x37001101117数字7
560x38001110008数字8
570x39001110019数字9
580x3A00111010:冒号
590x3B00111011;分号
600x3C00111100<小于号
610x3D00111101=等号
620x3E00111110>大于号
630x3F00111111?问号
640x4001000000@艾特符号
650x4101000001A大写字母A
660x4201000010B大写字母B
670x4301000011C大写字母C
680x4401000100D大写字母D
690x4501000101E大写字母E
700x4601000110F大写字母F
710x4701000111G大写字母G
720x4801001000H大写字母H
730x4901001001I大写字母I
740x4A01001010J大写字母J
750x4B01001011K大写字母K
760x4C01001100L大写字母L
770x4D01001101M大写字母M
780x4E01001110N大写字母N
790x4F01001111O大写字母O
800x5001010000P大写字母P
810x5101010001Q大写字母Q
820x5201010010R大写字母R
830x5301010011S大写字母S
840x5401010100T大写字母T
850x5501010101U大写字母U
860x5601010110V大写字母V
870x5701010111W大写字母W
880x5801011000X大写字母X
890x5901011001Y大写字母Y
900x5A01011010Z大写字母Z
910x5B01011011{左花括号
920x5C01011100\反斜杠
930x5D01011101}右花括号
940x5E01011110^脱字符
950x5F01011111_下划线
960x6001100000`重音符
970x6101100001a小写字母a
980x6201100010b小写字母b
990x6301100011c小写字母c
1000x6401100100d小写字母d
1010x6501100101e小写字母e
1020x6601100110f小写字母f
1030x6701100111g小写字母g
1040x6801101000h小写字母h
1050x6901101001i小写字母i
1060x6A01101010j小写字母j
1070x6B01101011k小写字母k
1080x6C01101100l小写字母l
1090x6D01101101m小写字母m
1100x6E01101110n小写字母n
1110x6F01101111o小写字母o
1120x7001110000p小写字母p
1130x7101110001q小写字母q
1140x7201110010r小写字母r
1150x7301110011s小写字母s
1160x7401110100t小写字母t
1170x7501110101u小写字母u
1180x7601110110v小写字母v
1190x7701110111w小写字母w
1200x7801111000x小写字母x
1210x7901111001y小写字母y
1220x7A01111010z小写字母z
1230x7B01111011{左花括号
1240x7C01111100|竖线
1250x7D01111101}右花括号
1260x7E01111110~波浪号
1270x7F01111111DEL删除

武技:测试格式化输出

// basic/Print.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>
#include <unistd.h>

void testFlush() {
    printf("Hello World\n");
    sleep(3);
    printf("Hello World with flush\n");
    fflush(stdout);
    sleep(3);
}

void testPrintf() {
    printf("十进制数:%hd, %d, %ld, %lld\n",
           (short)1, (int)1, (long)1L, (long long)1LL);
    printf("十进制数(无符号):%hu, %u, %lu, %llu\n",
           (short)2, (int)2, (long)2L, (long long)2LL);
    printf("八进制数:%ho, %o, %lo, %llo\n",
           (short)01, (int)01, (long)01L, (long long)01LL);
    printf("十六进制数(小写):%hx, %x, %lx, %llx\n",
           (short)0xa, (int)0xa, (long)0xAL, (long long)0xALL);
    printf("十六进制数(大写):%hX, %X, %lX, %llX\n", 0xa, 0xa, 0xAL, 0xALL);
    printf("浮点数:%f, %.2f\n",
           (double)3.14, (float)3.14F);
    printf("字符:%c, %d\n", (char)'A', (char)'A');
    printf("字符串:%s\n", "Hello World");
    printf("百分号:%%\n");
    int arr[] = {1};
    printf("指针:%p\n", arr);

    // 如果输出的变量值的字节长度小于占位符类型的长度,则会在二进制位高位补零。
    // 原值:10000000 00000000 00000000 00000001
    // 反码:11111111 11111111 11111111 11111110
    // 补码:11111111 11111111 11111111 11111111
    // 补零:00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111
    // 真值:4294967295
    printf("%lld\n", -1);

    // 如果输出的变量值的字节长度大于占位符类型的长度,则会在二进制位高位截取。
    // 原值:00000000 00000000 00000000 00000000 11111111 11111111 11111111 11111111
    // 补码:00000000 00000000 00000000 00000000
    // 截取:11111111 11111111 11111111 11111111
    // 反码:10000000 00000000 00000000 00000000
    // 补码:10000000 00000000 00000000 00000001
    // 真值:-1
    printf("%d\n", 4294967295LL);
}

2. puts

心法:C 语言支持使用 put() 将字符串输出到控制台,但只能输出字符串,并自动添加换行符,没有 printf 灵活,且 puts() 也会使用缓冲区。

puts() vs printf()

  • puts() 的功能相对单一,只能输出字符串,并且会自动添加换行符。
  • printf() 更加灵活,可以输出不同类型的数据,并可以通过格式说明符进行格式化输出。

武技:测试 puts 函数

// basic/Print.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>
#include <unistd.h>

void testPuts(){
	// puts(123); // error: 类型不匹配
    // 打印字符串到控制台
    puts("Hello World");
    puts("Hello World");
	// 刷新缓冲区
    fflush(stdout);
	// 睡眠3秒,用于测试缓冲区
    sleep(3);
}

3. 特殊字符

心法:特殊字符指的是在输出语句中具有自己独特含义的字符

特殊字符英文中文功能
\nnewLine换行符立刻将光标置于下一行的首位
\ttable制表符对前一个字符串补充 1-4 个空格,使其字节数为 4 的整数倍
一个英文字母或符号占 1 字节,一个UTF8字符占 2 字节
\0空字符一个空字符 NULL
\escape转义符废除紧随其后的一个字符的功能,使其变成一个纯文本

武技:测试输出特殊字符

// basic/Print.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>

void testSpecialCharacters(){

    // 换行符: 立刻将光标置于下一行的首位
    printf("世\n界\n");

    /*
     * 制表符: 对前一个字符串补充1-4个空格,使其字节数为4的整数倍
     * eq: 一个UTF8字符 "亚" 占2字节,`\t` 会补充2个空格到4字节
     * eq: "亚y" 占3字节,`\t` 会补充1个空格到4字节
     * eq: "亚ya" 占4字节,`\t` 会补充4个空格到8字节(因为至少补充1个空格)
     */
    printf("亚\t洲\n");
    printf("亚y\t洲\n");
    printf("亚ya\t洲\n");

    // 转义符: "\" 可以抵消紧随其后的一个符号的功能
    printf("\\n是换行符\n");
    printf("\\r是回行符\n");
    printf("\\t是制表符\n");
    printf("\\是转义字符\n");
    printf("\"\"是双引号\n");
}

E05. 输入语句

心法:输入语句用于将控制台上输入的内容输入到指定的变量中。

1. scanf

心法:当使用 printf 输出内容后,数据会先存入缓冲区,而非立即显示在控制台。

若在 printf 后紧接着使用 scanf,有可能会因为缓冲区没有被刷新,导致 printf 的内容没有输出,此时推荐在 printf 和 scanf 之间使用 fflush(stdout); 手动刷新缓冲区。

武技:测试 scanf 函数

// basic/Print.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>

void testScanf(){

    // 打印提示信息
    printf("请输入一个整数和一个浮点数(空格或回车分割):");
    // 手动刷新 stdout 缓冲区
    fflush(stdout);

    // 使用基本变量接收输入,则必须对接收变量使用 & 进行取地址
    // 因为基本类型变量是复制传参,scanf 对基本类型变量的任何操作都无法作用在原变量上。
    int a;
    double b;
    scanf("%d", &a);
    scanf("%lf", &b);
    printf("a = %d, b = %lf\n", a, b);

    // 打印提示信息
    printf("请输入一个字符串:");
    // 手动刷新 stdout 缓冲区
    fflush(stdout);

    char c[10];
    // 使用数组接收输入,则直接使用数组名称即可
    scanf("%s", c);
    printf("c = %s\n", c);
}

2. gets

心法:C 语言支持使用 gets() 从控制台读取一行字符串,直至遇到换行符为止,并存入字符数组中。

gets() vs scanf()

  • gets 会把末尾的换行符丢弃,并且在字符串末尾添加 \0 作为字符串结束标志。
  • scanf 是一个格式化输入函数,能够依据指定的格式字符串读取不同类型的数据,像整数、浮点数、字符串等。

武技:测试 gets 函数

// basic/Print.c
// Created by 周航宇 on 2025/2/26.
//

#include <stdio.h>

void testGets() {
    printf("请输入一个字符串:");
    // 手动刷新 stdout 缓冲区
    fflush(stdout);
    char str[10];
    // 接收控制台输入,然后将结果丢给str数组中
    gets(str);
    printf("接收到的字符串为:%s\n", str);
}

C/C++道经第1卷 - 第1阶 - 新手村(一)


传送门:CB1-1-新手村(一)
传送门:CB1-1-新手村(二)
传送门:CB1-1-新手村(三)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值