×
Featured image of post 换个姿势学C语言第6章-6.4 在Visual Studio 2022中创建静态库

换个姿势学C语言第6章-6.4 在Visual Studio 2022中创建静态库

换个姿势学C语言 第6章 创建自己的函数库–6.4 在Visual Studio 2022中创建静态库

0. 说明

《换个姿势学C语言》由何旭辉 著,清华大学出版社2022年出版。感谢何老师!

Snipaste_2024-03-10_14-51-10.png

这是一本非常不错的书!

6. 创建自己的函数库

渐渐地,读者已经发现在程序设计中实现某种功能的代码经常被重复使用。不建议在写程序时经常复制和粘贴那些代码,这会使程序变得臃肿和复杂,可以将这些功能设计成独立的函数,在需要使用时可以随时调用它们。

除此之外,还可以建立自己的函数库,将函数代码编译成库文件代他人或自己使用。

本章涉及:

  • 函数库的类型
  • 自定义处理字符串的函数
  • 自定义处理键盘输入的函数
  • 在Visual Studio 2020中创建静态库

本章将实现一些字符串的函数:

  • 计算字符串长度— strLength;
  • 在字符串是查找特定字符的位置— indexOfChar;
  • 转换字符串中大写字母为小写字母—toLowerCase;
  • 转换字符串大小写字母为大写字母—toUpperCase;
  • 复制字符串—strCopy。

还要实现几个用于提高键盘输入数据效率的函数:

  • 输入整数型—inputInteger;
  • 输入字符—inputChar;
  • 输入字符串—inputStr。

这些函数可以用于一下的外汇牌价看板的开发。

6.1 什么是函数库

  • 函数库是以重复利用程序为目的的且经过编译的函数的二进制代码 。
  • 函数库分为动态链接库和静态链接库两种。在生成可执行文件时,这两种库文件的处理方式是不同的。
6.1.1 静态链接库

静态链接库是最简单的处理方式。如果在程序中引用一个静态函数库,编译时程序会先被编译成一个目标文件(二进制代码),然后链接器会将这个目标文件和静态函数库中的二进制代码一起合并成一个可执行文件,这个可执行文件就可以脱离函数库而独立运行。

前面使用的外汇牌价接口库就是一个静态链接库,它的二进制代码会和调用它的代码一起生成一个可执行文件。

⚠️ 警告
静态函数库的缺点:当有很多个程序都引用同一个库时,每个程序的可执行文件都要包含一份同样的函数代码,这样就会浪费内存和磁盘空间;如果这个函数库需要升级,则所有使用该静态函数库的程序都要重新编译和链接!!
6.1.2 动态链接库

动态链接库克服了静态链接库的缺点。动态函数库文件独立地存在于某些系统目录下,在编译程序时动态函数库中的代码不被包含在可执行文件中,程序在使用到库函数时再从内存或磁盘的指定位置寻找动态函数库的代码。如在windows操作系统中,部分动态链接库被存放于C:\Windows\System32目录下。

动态链接库有两个好处,首先是可以节约内存和磁盘空间,其次是在函数库升级时不需要重新编译、链接使用该库的程序。

凡事总有例外,有时某些软件的安装包中并不包含运行时所需的动态链接库文件,而运行它的计算机上恰好也没有该动态链接库文件,这时候就会报错!!!

6.4 在Visual Studio 2022中创建静态库

现在我们已经创建了好几个函数了:

  • strLength函数,计算字符串长度
  • indexOfChar函数,在字符串中查找特定字符的位置
  • toLowerCase函数,将字符串中大写字母转换成小写字符
  • toUpperCase函数,将字符串中小写字母转换成大写字符
  • strCopy函数,将源字符串复制到目标字符串中
  • inputInteger函数,输入整数
  • inputChar函数,输入单个字符
  • inputString函数,输入字符串
  • clearInputBuffer函数,这个是我自己加的,正确的清空缓冲区函数。

这些函数之间可以相互调用,而每次我们需要使用另外一个函数时就不得不将代码复制粘贴过来,这不是一个好办法。

接下来,我们将创建一个静态链接库并将这些函数包含其中,未来就可以方便地在不同程序中快速引用这个静态链接库,而不用每次都到处复制源代码。

6.4.1 创建静态库项目

在Visual Studio中可以创建静态链接库,然后编译成.lib文件以便其他项目调用。

第1步:创建项目,然后给项目取一个名字(如Mars),并为其选择存储位置D:\BC101\Examples\L06,如下图所示:

Snipaste_2026-04-12_16-32-44.png

然后点击【创建】。

第2步:修改配置类型。如果采用默认设置,Visual Studio创建的项目会被编译成可执行文件(.exe),所以需要修改项目属性。在“解决方案资源管理器”中,选择刚刚创建的项目并右击,选择"属性",在弹出的对话框中将“常规”属性中的“配置类型”项由“应用程序(.exe)”修改为“静态库(.lib)”:

Snipaste_2026-04-12_16-35-23.png

第3步:属性修改完成后,就可以像以前一样在源代码中添加文件。

6.4.2 函数库中的代码组织

之前,我们几乎将所有的代码都写在同一个源代码文件中,但这并不是必须的。将源代码分门别类地放在不同文件中会使使用结构更清晰、更便于开发,尤其适合多人协作的项目。

在函数库中,应按照以下原则来组织文件:

  • 如果一个函数库中包含多种用途的函数,应该将其功能进行分类。每种类别的函数源代码应该独立放在一个源代码文件(.cpp文件或.c文件)中。例如我们之前完成的函数,用于字符串处理的函数代码可以放在str.cpp文件中,用于键盘输入的函数代码可以放在input.cpp文件中。
  • 单独使用一个头文件(.h文件)来包含类型、函数的声明。这个头文件的文件名通常与函数定义的源代码文件相同(扩展名不同)。
  • 源文件(.cpp文件或.c文件)中包含函数的定义代码,函数库的用户(其他程序员)不需要或不允许获得这些代码,他们将获得的编译过的库文件(.lib文件或.dll文件)。但是在使用这些函数时用户需要使用包含函数声明的头文件(就像我们需要stdio.h一样),因此我们将函数的声明写在单独的头文件中,而这些函数的定义写在另一个源代码文件中。在编译顺序上是先添加头文件,再添加同名的源代码文件(扩展名不同)。

因此,我们将建议以下文件:

  • str.hstr.cpp,将字符串处理函数的声明和定义分别放入这两个文件中,之所以采用str作为文件名是为了避免和标准库函数string.h产生冲突。
  • input.hinput.cpp,将输入函数的声明和定义分别放入这两个文件中。

接下来分别在之前创建的静态库项目Mars中,增加str.hstr.cppinput.hinput.cpp文件并在其中加入代码,在添加新文件时需要注意文件类型分别是“头文件(.h)”和"C++文件(.cpp)"。

Snipaste_2026-04-12_20-17-38.png

字符相关:

str.h头文件代码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
#pragma once // 防止头文件重复包含
// 计算字符串长度
unsigned int strLength(const char* str);

// 在字符串中查找特定字符的位置
int indexOfChar(const char* str, char ch);

// 将字符串中大写字母转换成小写字符
void toLowerCase(char* str);

// 将字符串中小写字母转换成大写字符
void toUpperCase(char* str);

// 将源字符串复制到目标字符串中
char* strCopy(char* destination, const char* source);

str.cpp实现代码如下:

  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
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
// 通用工具库,字符串处理
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>

// 计算字符串长度
unsigned int strLength(const char* str)
{
    // 定义存储字符串长度的变量
    unsigned int length = 0;

    if (str == NULL) { // 增加空指针判断,避免崩溃
        return 0;
    }
    // 判断指针str指向的值是否为字符串结束符'\0'
    while (*str != '\0')
    {
        // 打印当前字符和内存地址
        // printf("当前字符%c的地址为:%p\n", *str, str);
        // 长度值增1
        length++;
        // 指针变量str的值+1,表示指向下一个元素
        str++;
        // 最后打印出计算出的总长度
        // printf("length:%u\n", length);
    }
    return length;
}

// 在字符串中查找特定字符的位置
int indexOfChar(const char* str, char ch)
{
    // 定义存储索引的位置
    int index = 0;

    if (str == NULL) { // 增加空指针判断,避免崩溃
        return -1;
    }

    // 遍历字符串一定要用临时指针,不要动原始指针!
    // 用临时指针遍历,不修改原始 str
    const char* p = str;

    // 判断指针str指向的值是否为字符串结束符'\0'
    while (*p != '\0')
    {
        // 打印当前字符和内存地址
        // printf("当前字符%c的地址为:%p\n", *p, p);
        // 判断是否相等
        if (*p == ch)
        {
            printf("在字符串%s当前索引是 %d,对应字符是:%c ,与目前字符 %c 匹配\n", str, index, *p, ch);
            return index;
        }

        printf("在字符串%s当前索引是 %d,对应字符是:%c ,与目前字符 %c 不匹配\n", str, index, *p, ch);
        // 临时指针变量p的值+1,表示指向下一个元素
        p++;
        index++;
    }
    return -1;
}

// 将字符串中大写字母转换成小写字符
void toLowerCase(char* str)
{
    if (str == NULL) { // 增加空指针判断,避免崩溃
        return;
    }
    // 由于需要对原始字符串进行修改,此处不使用临时指针

    // 判断指针str指向的值是否为字符串结束符'\0'
    while (*str != '\0')
    {
        // 打印当前字符和内存地址
        // printf("当前字符%c的地址为:%p\n", *p, p);
        // 判断是否是大写字母
        if (*str >= 'A' && *str <= 'Z')
        {
            *str += 'a' - 'A';
        }

        // 指针变量str的值+1,表示指向下一个元素
        str++;
    }
}

// 将字符串中小写字母转换成大写字符
void toUpperCase(char* str)
{
    if (str == NULL) { // 增加空指针判断,避免崩溃
        return;
    }
    // 由于需要对原始字符串进行修改,此处不使用临时指针

    // 判断指针str指向的值是否为字符串结束符'\0'
    while (*str != '\0')
    {
        // 打印当前字符和内存地址
        // printf("当前字符%c的地址为:%p\n", *p, p);
        // 判断是否是小写字母
        if (*str >= 'a' && *str <= 'z')
        {
            *str -= 'a' - 'A';
        }

        // 指针变量str的值+1,表示指向下一个元素
        str++;
    }
}

// 将源字符串复制到目标字符串中
char* strCopy(char* destination, const char* source)
{
    char* returnValue = destination;
    if (source == NULL || destination == NULL) { // 增加空指针判断,避免崩溃
        return NULL;
    }

    // 遍历字符串一定要用临时指针,不要动原始指针!
    // 用临时指针遍历,不修改原始 source
    const char* p = source;

    // 判断指针p指向的值是否为字符串结束符'\0'
    while (*p != '\0')
    {
        // 直接将当前字符复制到目录字符对应位置
        *destination = *p;

        // 指针变量p的值+1,表示指向下一个元素
        p++;
        destination++;
    }

    // 最后补字符串结束符
    *destination = '\0';
    return returnValue;
}

用户输入input.h头文件如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#pragma once // 防止头文件重复包含

// 正确的清空缓冲区函数
void clearInputBuffer();

// 输入整数
int inputInteger(const char* prompt, int min, int max);

// 输入单个字符
char inputChar(const char* prompt, const char* validChars);

// 输入字符串
char* inputString(char* destination, const char* prompt, int minLength, int maxLength);

用户输入input.cpp实现代码如下:

  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
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// 通用工具库,用户输入处理
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>
// 引入本项目中的str.h头文件,避免indexOfChar和strLength函数无法使用
#include "str.h"

// 正确的清空缓冲区函数
void clearInputBuffer()
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

// 输入整数
int inputInteger(const char* prompt, int min, int max)
{
    if (prompt == NULL) { // 增加空指针判断,避免崩溃
        printf("提示语不能为空\n");
        return -1;
    }

    if (min > max) {
        printf("最小值不能大于最大值\n");
        return -1;
    }

    int inputFlag = 0;
    int inputValue = 0;
    do {
        // 读取用户输入的整数
        printf("%s", prompt);
        // 使用 " %d" 跳过前面所有空白(空格、回车、Tab)
        inputFlag = scanf(" %d", &inputValue);

        if (inputFlag == 1) {
            // 【关键】检查后面是不是还有非法字符(. 或 字母)
            int ch = getchar();
            if (ch != '\n' && ch != EOF) {
                // 还有内容 → 不是纯整数!
                inputFlag = 0; // 强制标记为输入失败
                // 清空缓冲区
                clearInputBuffer();
            }
        }
        else {
            // 清空缓冲区
            clearInputBuffer();
        }

        // 用户输入错误时进行提示,提升用户体验
        if (inputFlag != 1) {
            printf("==> 输入无效,请输入整数!\n");
        }
        else if (inputValue < min || inputValue > max) {
            printf("==> 输入超出范围,请输入 %d ~ %d 之间的整数!\n", min, max);
        }
    } while (inputFlag != 1 || inputValue < min || inputValue > max);
    return inputValue;
}

// 输入单个字符
char inputChar(const char* prompt, const char* validChars)
{
    if (prompt == NULL || validChars == NULL) { // 增加空指针判断,避免崩溃
        printf("提示语 或 允许输入的字符 不能为空\n");
        return '\0';
    }

    int inputFlag = 0;
    char inputValue = 0;
    do {
        // 读取用户输入字符
        printf("%s", prompt);
        // 使用 " %c" 跳过前面所有空白(空格、回车、Tab)
        inputFlag = scanf(" %c", &inputValue);

        if (inputFlag == 1) {
            // 【关键】检查后面是不是还有非法字符
            int ch = getchar();
            if (ch != '\n' && ch != EOF) {
                // 还有内容 → 不是单字符!
                printf("读取到多余字符:%c\n", ch);
                inputFlag = 0; // 强制标记为输入失败
                // 清空缓冲区
                clearInputBuffer();
            }
        }
        else {
            // 清空缓冲区
            clearInputBuffer();
        }

        // 用户输入错误时进行提示,提升用户体验
        if (inputFlag != 1) {
            printf("==> 输入无效,请输入【%s】中的一个字符!\n", validChars);
        }
        else if (indexOfChar(validChars, inputValue) < 0) {
            printf("==> 输入无效,你输入的是 %c, 请输入【%s】中的一个字符!\n", inputValue, validChars);
            inputFlag = 0; // 强制标记为输入失败
        }
    } while (inputFlag != 1);
    return inputValue;
}

// 输入字符串
char* inputString(char* destination, const char* prompt, int minLength, int maxLength)
{
    // 定义允许的最小长度和最大长度,调用者定义的最小最大长度不能超过这个区间
    const int MIN = 1;
    const int MAX = 1024;
    if (prompt == NULL || destination == NULL) { // 增加空指针判断,避免崩溃
        printf("提示语 或 目标字符串首地址 不能为空\n");
        return NULL;
    }

    if (minLength < MIN) {
        printf("最小长度值不能小于 %d \n", MIN);
        return NULL;
    }

    if (maxLength > MAX) {
        printf("最大长度值不能大于 %d \n", MAX);
        return NULL;
    }

    if (minLength > maxLength) {
        printf("最小长度不能大于最大长度\n");
        return NULL;
    }

    // 输入标记,0为输入异常,1为输入正常
    int inputFlag = 0;

    do {
        // 读取用户输入字符
        printf("%s", prompt);
        // 使用 " %1024[^\n]" 跳过前面所有空白(空格、回车、Tab),并且宽度最多1024个字符
        // %1024s 表示读取不包含空格的字符
        // [^\n] 意思:除了换行符,所有字符都读
        inputFlag = scanf(" %1024[^\n]", destination);
        if (inputFlag != 1) {
            // 读取失败清空缓冲区
            clearInputBuffer();
        }

        int inputLen = strLength(destination);
        if (inputLen < minLength || inputLen > maxLength) {
            inputFlag = 0; // 强制标记为输入失败
            printf("您输入的字符串长度不满足要求 [%d, %d],当前长度: %d\n", minLength, maxLength, inputLen);
        }
    } while (inputFlag != 1);
    return destination;
}

生成静态库:

Snipaste_2026-04-12_20-20-52.png

点击【生成】–【生成解决方案】,就可以生成静态库,此时在目录D:\BC101\Examples\Debug会生成静态库文件:

  • Mars.lib, 最重要的,静态库本体 = 函数的二进制代码,里面是 strLengthinputString真正实现,**发布、给别人用、测试调用,只需要这个文件!**没有它 → 程序无法链接,直接报错。
  • Mars.pdb,调试数据库,Debug 模式才会生成,发布给别人时可以删,不影响运行,只在 Debug 模式生成。
  • Mars.idb,VS 编译器的临时缓存文件,用来增量编译,可以直接删除,不影响库、不影响调试。

Snipaste_2026-04-12_20-23-09.png

6.4.3 分发函数库

Visual Studio默认生成的是包含调试信息的Debug版本(调试版本)的库文件,调试版本不进行任何代码优化,便于程序员调试。在生成调试版本的.exe或.lib文件时,还会生成一个.pdb文件,该文件记录了代码中断点等调试信息。如果想要优化代码,可以生成Release版本(发布版本)。

如果其他程序员需要使用这个函数库,需要将Mars.lib文件,str.hinput.h头文件一起提供给他就行了!有必要的话还需要写一份“手册”,证明每个函数的使用方法,最好能够提供范例程序。

创建目录D:/BC101/Libraries/Mars,将Mars.lib文件,str.hinput.h头文件复制到这个目录,这样就可以将它们复制给需要使用这些函数的人了!!

Snipaste_2026-04-12_20-45-27.png

现在我们已经完成了函数库的创建、编译和分发,接下来就是在其他项目中使用它了!!!

6.4.4 在其他项目中引用函数库

现在创建一个新的项目L06_12_USE_MARS_LIB,使用默认的配置类型(保持“应用程序(.exe)”)即可。

 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
// L06_12_USE_MARS_LIB.cpp
// 调用自定义静态库
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>
// 引入自定义的静态库的头文件
#include "D:/BC101/Libraries/Mars/str.h"
#include "D:/BC101/Libraries/Mars/input.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
#pragma comment(lib,"D:/BC101/Libraries/Mars/Mars.lib")

int main()
{
    // 定义一个字符串
    char  string[10] = { 'H', 'e', 'l', 'l','o', '\0' };

    printf("字符数组string的首地址为:%p\n", string);
    printf("字符数组string中存储的字符串是:%s\n", string);
    unsigned int length = strLength(string);
    printf("字符数组string的长度是:%u\n", length);

    // 测试空指针
    const char* str = NULL;
    printf("总长度:%u\n", strLength(str));

    return 0;
}

运行代码:

Snipaste_2026-04-12_20-51-10.png

可以看到,可以正常运行代码!

但以上代码中存在引用头文件和库文件使用绝对目录:

1
2
3
#include "D:/BC101/Libraries/Mars/str.h"
#include "D:/BC101/Libraries/Mars/input.h"
#pragma comment(lib,"D:/BC101/Libraries/Mars/Mars.lib")

会出现以下问题:

  • 代码拷到别的电脑 → 直接编译失败

  • 项目移动位置 → 编译失败

  • 生产 / 公司项目严禁写绝对路径

最好是设置附加包含目录将库文件目录加进去:依次点击 项目属性 → C/C++ → 常规 → 附加包含目录

Snipaste_2026-04-12_21-51-12.png

将目录D:/BC101/Libraries/Mars加入到附加包含目录列表中,然后点击“确定”:

Snipaste_2026-04-12_21-52-24.png

这样编译器就知道在哪里去找静态库的头文件了,优化后的代码:

 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
// L06_12_USE_MARS_LIB.cpp
// 调用自定义静态库
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>
// 附加包含目录已经添加 D:/BC101/Libraries/Mars
// 此处引入自定义的静态库的头文件需要相对路径
#include "str.h"
#include "input.h"
// #pragma comment:VS 编译器专用指令,给编译器发命令
//      lib:表示我要链接一个静态库
//      路径/Mars.lib":库文件在哪里
#pragma comment(lib,"Mars.lib")

int main()
{
    // 定义一个字符串
    char  str[10] = { 'H', 'e', 'l', 'l','o', '\0' };
    printf("===== 测试调用静态库strLength函数 =====\n");
    printf("字符数组str的首地址为:%p\n", str);
    printf("字符数组str中存储的字符串是:%s\n", str);
    unsigned int length = strLength(str);
    printf("字符数组str的长度是:%u\n", length);

    // 测试空指针
    const char* str_null = NULL;
    printf("总长度:%u\n", strLength(str_null));

    return 0;
}

但此时运行代码,却报错了,提示“ LNK1104 无法打开文件 Mars.lib

Snipaste_2026-04-12_22-10-47.png

原因:编译器找不到 Mars.lib! 虽然代码里写了#pragma comment(lib,"Mars.lib"),但 VS 不知道这个 lib 放在哪个文件夹里! 只需要处理最后一步配置,必须告诉 VS:Mars.lib 所在的文件夹路径 路径:测试项目 → 右键 → 属性 → 链接器 → 常规 → 附加库目录

将目录D:/BC101/Libraries/Mars加入到附加库目录列表中:

Snipaste_2026-04-12_22-16-00.png

添加后,显示如下:

Snipaste_2026-04-12_22-16-51.png

然后再运行代码,就可以正常运行了:

Snipaste_2026-04-12_22-18-51.png

说明库函数引用成功!!!

总结:

⚠️ 警告

.h 文件 和 .lib 文件 是两个完全不同的文件!

  • 编译器编译时 → 找 .h → 用 C/C++ 【附加包含目录】
  • 链接器链接时 → 找 .lib → 用链接器【附加库目录】
  • 附加包含目录 = 找头文件(.h), 附加库目录 = 找库文件(.lib),两个都要配,缺一不可!

但这个时候,如果增加以下测试代码:

1
2
char str_input[11];
inputString(str_input, "请输入10个字符以内的字符串:", 1, 10);

运行就会出现异常:

Snipaste_2026-04-12_22-58-18.png

变量 str_input 周围的栈空间被破坏了(缓冲区溢出!)inputString内部没有限制最大读取长度! 结果:用户输入 超过 10 个字符→ 函数直接往数组里写→ 破坏了数组外面的内存(栈损坏)

这个时候就需要优化库函数。但库函数一般可能是别人给你的,需要反馈给开发者。

此处我来优化一下我的input.cpp文件中的inputString函数:

  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
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
// 通用工具库,用户输入处理
// 忽略scanf不安全告警
#define _CRT_SECURE_NO_WARNINGS
// 屏蔽scanf返回值被忽略警告
#pragma warning(disable: 6031)
//
#include <stdio.h>
// 引入本项目中的str.h头文件,避免indexOfChar和strLength函数无法使用
#include "str.h"

// 正确的清空缓冲区函数
void clearInputBuffer()
{
    int c;
    while ((c = getchar()) != '\n' && c != EOF);
}

// 输入整数
int inputInteger(const char* prompt, int min, int max)
{
    if (prompt == NULL) { // 增加空指针判断,避免崩溃
        printf("提示语不能为空\n");
        return -1;
    }

    if (min > max) {
        printf("最小值不能大于最大值\n");
        return -1;
    }

    int inputFlag = 0;
    int inputValue = 0;
    do {
        // 读取用户输入的整数
        printf("%s", prompt);
        // 使用 " %d" 跳过前面所有空白(空格、回车、Tab)
        inputFlag = scanf(" %d", &inputValue);

        if (inputFlag == 1) {
            // 【关键】检查后面是不是还有非法字符(. 或 字母)
            int ch = getchar();
            if (ch != '\n' && ch != EOF) {
                // 还有内容 → 不是纯整数!
                inputFlag = 0; // 强制标记为输入失败
                // 清空缓冲区
                clearInputBuffer();
            }
        }
        else {
            // 清空缓冲区
            clearInputBuffer();
        }

        // 用户输入错误时进行提示,提升用户体验
        if (inputFlag != 1) {
            printf("==> 输入无效,请输入整数!\n");
        }
        else if (inputValue < min || inputValue > max) {
            printf("==> 输入超出范围,请输入 %d ~ %d 之间的整数!\n", min, max);
        }
    } while (inputFlag != 1 || inputValue < min || inputValue > max);
    return inputValue;
}

// 输入单个字符
char inputChar(const char* prompt, const char* validChars)
{
    if (prompt == NULL || validChars == NULL) { // 增加空指针判断,避免崩溃
        printf("提示语 或 允许输入的字符 不能为空\n");
        return '\0';
    }

    int inputFlag = 0;
    char inputValue = 0;
    do {
        // 读取用户输入字符
        printf("%s", prompt);
        // 使用 " %c" 跳过前面所有空白(空格、回车、Tab)
        inputFlag = scanf(" %c", &inputValue);

        if (inputFlag == 1) {
            // 【关键】检查后面是不是还有非法字符
            int ch = getchar();
            if (ch != '\n' && ch != EOF) {
                // 还有内容 → 不是单字符!
                printf("读取到多余字符:%c\n", ch);
                inputFlag = 0; // 强制标记为输入失败
                // 清空缓冲区
                clearInputBuffer();
            }
        }
        else {
            // 清空缓冲区
            clearInputBuffer();
        }

        // 用户输入错误时进行提示,提升用户体验
        if (inputFlag != 1) {
            printf("==> 输入无效,请输入【%s】中的一个字符!\n", validChars);
        }
        else if (indexOfChar(validChars, inputValue) < 0) {
            printf("==> 输入无效,你输入的是 %c, 请输入【%s】中的一个字符!\n", inputValue, validChars);
            inputFlag = 0; // 强制标记为输入失败
        }
    } while (inputFlag != 1);
    return inputValue;
}

// 输入字符串
char* inputString(char* destination, const char* prompt, int minLength, int maxLength)
{
    // 定义允许的最小长度和最大长度,调用者定义的最小最大长度不能超过这个区间
    const int MIN = 1;
    const int MAX = 1024;
    if (prompt == NULL || destination == NULL) { // 增加空指针判断,避免崩溃
        printf("提示语 或 目标字符串首地址 不能为空\n");
        return NULL;
    }

    if (minLength < MIN) {
        printf("最小长度值不能小于 %d \n", MIN);
        return NULL;
    }

    if (maxLength > MAX) {
        printf("最大长度值不能大于 %d \n", MAX);
        return NULL;
    }

    if (minLength > maxLength) {
        printf("最小长度不能大于最大长度\n");
        return NULL;
    }

    // 输入标记,0为输入异常,1为输入正常
    int inputFlag = 0;

    do {
        // 读取用户输入字符
        printf("%s", prompt);
        // 使用 " %1024[^\n]" 跳过前面所有空白(空格、回车、Tab),并且宽度最多1024个字符
        // %1024s 表示读取不包含空格的字符
        // [^\n] 意思:除了换行符,所有字符都读
        // 此处不能直接写死!!!
        // inputFlag = scanf(" %1024[^\n]", destination);
        // ====================== 核心安全输入,避免用户传入一个小数组 ======================
        if (maxLength <= 10) {
            inputFlag = scanf(" %10[^\n]", destination);
        }
        else if (maxLength <= 50) {
            inputFlag = scanf(" %50[^\n]", destination);
        }
        else if (maxLength <= 100) {
            inputFlag = scanf(" %100[^\n]", destination);
        }
        else {
            inputFlag = scanf(" %1024[^\n]", destination);
        }
        if (inputFlag != 1) {
            // 读取失败清空缓冲区
            clearInputBuffer();
        }

        int inputLen = strLength(destination);
        if (inputLen < minLength || inputLen > maxLength) {
            inputFlag = 0; // 强制标记为输入失败
            printf("您输入的字符串长度不满足要求 [%d, %d],当前长度: %d\n", minLength, maxLength, inputLen);
        }
    } while (inputFlag != 1);
    return destination;
}

然后重新生成库文件,并复制到D:\BC101\Libraries\Mars目录,再次运行测试代码,可以发现能够正常运行!!

Snipaste_2026-04-12_23-10-01.png