# C语言2021 **Repository Path**: whuls/c-language-2021 ## Basic Information - **Project Name**: C语言2021 - **Description**: C语言2021课堂问题解惑 及 习题参考 - **Primary Language**: C - **License**: Apache-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-09-26 - **Last Updated**: 2021-12-24 ## Categories & Tags **Categories**: Uncategorized **Tags**: C课堂 ## README # C语言2021 [TOC] # 介绍 C语言2021课堂问题解惑 及 习题参考 # 期末 上机考试 [期末 上机考试](上机考试.md) # 常见问题及解决 ## 使用scanf还是scanf_s? 两个函数都是用于从命令行**获取用户输入**,运行时程序会中断,等待用户输入后继续运行。 使用`scanf`存在安全隐患,有的编译器会提示: `错误 C4996 'scanf': This function or variable may be unsafe. Consider using scanf_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.` 关掉这个提示的方法(任意一个都行): 1. 加上预编译头`#pragma warning(disable:4996)` 2. “项目”-“属性”-“C\C++”-“预处理器”-“预处理器定义”,加一行`_CRT_SECURE_NO_WARNINGS` > 存在的安全隐患:“scanf()在读取字符串时不检查边界,可能会造成内存泄露”,参考:[scanf() 与 scanf_s() 的区别](https://blog.csdn.net/silleyj/article/details/8545408) 使用`scanf_s`需要使用正确,在读取**字符**和**字符串**时需要指定读取的长度。一个例子: ```c++ #include int main() { char buffer[128]; printf("Please input a string:\n"); /* 这里必须要有128,以表明最多从命令行的用户输入读取128个字符 如果写成scanf_s("%s",buffer),程序将无法执行到底 且编译器会提示“Unhandled exception at 0xfefefefe in array.exe:0xC0000005: Access tion.”。 当然在安全性要求不高的情况下,不一定非要用scanf_s()函数,可用scanf("%s",buffer)代替。 */ scanf_s("%s", buffer, 128); return 0; } ``` # 代码参考 ## 输出ASCII码(阿斯科码) ```c++ #include int main() { char c = 'a'; int a = 97; printf("%d\n", c); // 输出:97。将`字符c`作为int输出,将会输出其ASCII码 printf("%c\n", a); // 输出:'a'。将`数字a`作为char输出,将会输出字符 return 0; } ``` ## 习题:逆序数1234转4321 ### 已知位数,两种解法 1 ```c++ #include /* * 逆序数:已知位数 */ int main() { int num = 1234; int a, b, c, d; a = num / 1000; // 个位 b = (num - a * 1000) / 100; // 十位 c = (num - a * 1000 - b * 100) / 10; // 百位 d = num % 10; // 千位 printf("最后的结果是:%d\n", d * 1000 + c * 100 + b * 10 + a); // 4321 return 0; } ``` 2 ```c++ #include /* * 逆序数:已知位数 */ int main() { int num = 1234; int result = 0; // 千位 result += num % 10 * 1000; num /= 10; // 百位 result += num % 10 * 100; num /= 10; // 十位 result += num % 10 * 10; num /= 10; // 个位 result += num % 10; printf("最后的结果是:%d\n", result); // 4321 return 0; } ``` ### 未知位数 思路:首先判断位数,接着逆序计算 ```c++ #include #include #include /* * 逆序数:未知位数 */ int main() { // 生成随机数 srand(time(0)); int num = rand(); // 判断位数 int N = num; int count = 0; while (N > 0) { count++; N /= 10; } // 计算结果 N = num; int result = 0; int e; // 次方 for (int i = count; i > 0; i--) { e = 1; for (int j = 0; j < i - 1; j++) // 如:1234的千位,总共有4位,需要乘以1e3,因此次数要比i少1 { e *= 10; } result += N % 10 * e; N /= 10; } printf("随机数:%d的逆序数是:%d\n", num, result); return 0; } ``` > `stdlib.h`和`time.h`用于调用时间函数和随机数函数 ## 习题:循环控制 ### 1. 切分点坐标 ```C++ /* 用N边形(N>4)拟合一个圆,输入圆心坐标x0,y0、半径R和圆切分的边数N,输出圆周上的各切分点的坐标。 */ #pragma warning(disable:4996) #include #include #define PI 3.1415926 int main() { int flag; double x0, y0, r; int N; printf("输入一个圆心坐标 x0 y0:\n"); flag = scanf("%lf %lf", &x0, &y0); printf("输入半径 r:\n"); flag = scanf("%lf", &r); printf("输入边数 N:\n"); flag = scanf("%d", &N); for (int i = 0; i < N; i++) { printf("(x_%d, y_%d) = (%lf, %lf)\n", i, i, r * cos(2 * PI / N * i), r * sin(2 * PI / N * i)); } return 0; } ``` ### 2. 分解质因数 法一: ```C++ /* 编写一个程序,提示用户输入一个正整数,然后将正整数分解质因数输出。 eg:Enter a positive integer: 120 120=2*2*2*3*5 */ #pragma warning(disable:4996) #include int main() { int n; printf("输入一个正整数:\n"); int flag = scanf("%d", &n); printf("%d = ", n); while (true) { // 判断能否被质数整除 int i; for (i = 2; i < n; i++) { if (n % i == 0) { printf("%d * ", i); n = n / i; break; } } // 如果小于等于2或者无法被小于该数字的质数整除 // ,则说明是最后一个数 if (n <= 2 || i == n) { printf("%d\n", n); break; } } return 0; } ``` 法二: ```C++ /* 编写一个程序,提示用户输入一个正整数,然后将正整数分解质因数输出。 eg:Enter a positive integer: 120 120=2*2*2*3*5 */ #pragma warning(disable:4996) #include int main() { int n; printf("输入一个正整数:\n"); int flag = scanf("%d", &n); printf("%d = ", n); int i = 2; while (i <= n) { if (n % i == 0) { if (i < n) { printf("%d * ", i); n = n / i; i = 2; } else if (i == n) { printf("%d", i); break; } } else { i++; } } return 0; } ``` ### 3. 判断搬砖人数 ```c++ /* 某工地需要搬运砖块,已知男人每人每次搬3块,女人每人每次搬2块,小孩两人每次抬一块,现有45人一次正好搬完45块砖,请问男人、女人、小孩各几人? 编程穷举输出结果,不列方程。 */ #pragma warning(disable:4996) #include #include int main() { int nMan, nWoman, nChild; nMan = nWoman = nChild = 0; // 男人 0-15个 for (int i = 0; i <= 15; i++) { // 女人 0-22个 for (int j = 0; j <= 22; j++) { // 小孩 = 45 - i - j if (i * 3 + j * 2 + (45 - i - j) * 0.5 == 45.0) { printf("男人%d个,女人%d个,小孩%d个\n", i, j, 45 - i - j); } } } return 0; } ``` ## 练习:数组字符串 ### 1. 判断两个字符串是否一样 ```c++ #include //分别输入两个字符串,判断两个字符串是否一样,并输出判断结果。 int main() { char str1[200], str2[200]; scanf("%s", str1); scanf("%s", str2); int n1 = 0; //第1个字符串长度 int i = 0; while (str1[i] != '\0') // != 0 { i++; } n1 = i; //字符串长度 int n2 = 0; //第2个字符串长度 i = 0; while (str1[i] != '\0') // != 0 { i++; } n2 = i; bool bSame = true; if (n1 == n2) { //长度一样,每个字符比较 for (i = 0; i < n1; i++) { if (str1[i] != str2[i]) { bSame = false; break; } } } else { bSame = false; } if (bSame) printf("一样!"); else printf("不一样!"); return 0; } ``` ### 2. 字符串小写字母转大写字母 ```c++ #include //输入一个字符串,将字符串中的小写字母转换为大写字母。 int main() { char str[20]; scanf("%s", str); int n = 0; int i = 0; while (str[i] != '\0') // != 0 { i++; } n = i; //字符串长度 // 97 a // 65 A for (i = 0; i < n; i++) { if ( str[i] >=97 && str[i] <=122) { //小写转大写 str[i] -= 32; } } printf("%s", str); return 0; } ``` ### 3. 求字符串的长度 ```c++ #include //输入一个字符串,统计并输出字符串的长度 int main() { char str[20]; scanf("%s", str); int i = 0; while (str[i] != '\0') // != 0 { i++; } int n = i; printf("长度是: %d", n); return 0; } ``` ### 4. 求数组元素的最大值与平均值 ```c++ #include int main() { //1.定义包含5个元素的数组,输入数据,用循环求最大值, 输出。 //2.定义包含5个元素的数组,输入数据,求数据的平均值, 输出 double a[5] = { 1.2, 4.5, 8.8, -0.9, 9.0 }; //初始化 int i;//循环变量 double max = a[0]; // -1E10; //输入 //for (i = 0; i < 5; i++) // scanf("%lf", &a[i]); double fSum = 0.0; for (i = 0; i < 5; i++) { if (a[i] > max) max = a[i]; //求最大值 fSum += a[i]; //求和 } double fAvg = fSum / 5; printf("最大值 %lf\n", max); printf("平均值 %lf\n", fAvg); return 1; } ``` ### 5. 统计单词个数 ```c++ #pragma warning(disable:4996) #include int main() { char str[] = " The quick brown... ^ dog , , , , jumps over the lazy fox "; // 统计单词数量 int num = 0; bool bWord = false; for (int i = 0; str[i] != '\0'; i++) { if ((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z')) { bWord = true; } else { if (bWord) { num++; bWord = false; } } } // 处理字符串不以空格结尾的情况 if (bWord) num++; return 0; } ``` ### 6. 子字符串首次出现的位置 ```c++ #pragma warning(disable:4996) #include #include int main() { char str[] = "lazy"; char str2[] = "The quick brown dog jumps over the lazy fox fox fox fox fox fox"; // printf("%d\n", int(strstr(str2, str) - str2) + 1); // 遍历大字符串 for (int i = 0; str2[i] != '\0'; i++) { // 遍历子字符串 bool bSame = true; for (int j = 0; str[j] != '\0'; j++) { if (str2[i + j] != str[j]) { bSame = false; break; } } if (bSame) { printf("%d\n", i + 1); break; } } return 0; } ``` ## 期中测试 ### 1. 给定一个数组,将其从大到小进行输出,同时打印出最大值及其出现的次数 ```C++ #include #include #include int main() { // 定义一个数组 srand(time(0)); int n = 1024; int* arr = new int[n]; for (int i = 0; i < n; i++) { arr[i] = rand() % 1000; } // 排序 int t; for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[j] > arr[i]) { t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } } // 输出排序后的数组 for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n\n"); // 最大数和出现的次数 int max = arr[0]; int count; for (count = 0; arr[count] == max; count++); printf("最大数是:%d, 出现的次数:%d\n", max, count); delete[] arr; return 0; } ``` ### 2. 给定一个字符串,搜索字符串中长度最大的单词,输出该单词并统计该单词字符个数 ```c++ #include #include int main() { char str[] = { "Remote sensing is the acquiring of information from a distance. NASA observes Earth and other planetary bodies via remote sensors on satellites and aircraft that detect and record reflected or emitted energy. Remote sensors, which provide a global perspective and a wealth of data about Earth systems, enable data-informed decision making based on the current and future state of our planet." }; printf("%s\n\n", str); int nLen = strlen(str); // 字符串总长度 char strMax[128]; // 记录最大长度单词 int nLenStrMax = 0; // 最长单词长度 /// 补充代码 int curLen; // 用于记录当前单词的长度 int curPos; // 用于记录当前单词的起始位置 curLen = 0; curPos = -1; for (int i = 0; i < nLen; i++) { if ((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z') || str[i] == '-') { curLen++; if (curPos == -1) curPos = i; } else { if (curLen > nLenStrMax) { nLenStrMax = curLen; // 字符串拷贝,或者用 strncpy_s() 函数 for (int n = 0; n < curLen; n++) { strMax[n] = str[n + curPos]; } strMax[curLen] = '\0'; } curLen = 0; curPos = -1; } } /// /////////// printf("最大长度单词: %s, 长度为 %d \n", strMax, nLenStrMax); return 0; } ``` ## 练习:指针 ### 1. 定义一个m×n的矩阵,用指针输出其每一个成员 > [C语言中文网 | C语言二维数组指针(指向二维数组的指针)详解](http://c.biancheng.net/view/2022.html) 3×3矩阵: ```c++ #include #include int main() { int Matrix[3][3] = { {17,32,3}, {6,59, 88}, {72,43, 98} }; int(*ptr)[3] = Matrix; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { //printf("%d ", ptr[i][j]); printf("%d ", *(*(ptr + i) + j)); } printf("\n"); } return 0; } ``` 动态分配内存,创建随机矩阵: ```C++ #include #include #include int main() { int m, n; printf("请输入矩阵的行列(先行后列,用空格隔开)\n"); scanf_s("%d %d", &m, &n); // 初始化矩阵 srand(time(0)); int** mat = new int* [m]; for (int i = 0; i < m; i++) { mat[i] = new int[n]; for (int j = 0; j < n; j++) { mat[i][j] = rand() % 100; } } // 访问并输出 for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { // printf("%d ", mat[i][j]); printf("%d ", *(*(mat + i) + j)); } printf("\n"); } // 释放内存 for (int i = 0; i < m; i++) { delete[] mat[i]; } delete[] mat; return 0; } ``` ### 2. 定义长度为10的数组,然后输入5个数,从大到小排序,然后再逐个输入后面的5个数,每一个都插入到有序数组中,并保持顺序(用指针访问数组) ```C++ #include #include #include int main() { // 定义长度为10的数组 int arr[10] = { 0 }; int* ptr = arr; // 输入前5个数 srand(time(0)); for (int i = 0; i < 5; i++) { *(ptr + i) = rand() % 1000; } // 对前五个数从大到小排序 for (int i = 0; i < 5; i++) { for (int j = i; j < 5; j++) { if (*(ptr + j) > *(ptr + i)) { int t = *(ptr + j); *(ptr + j) = *(ptr + i); *(ptr + i) = t; } } } printf("排序过程:\n"); for (int i = 0; i < 5; i++) printf("%d ", *(ptr + i)); printf("\n"); // 再输入五个数,每一个都有序插入到列表中 int arrLen = 5; for (int i = 0; i < 5; i++) { int n = rand() % 1000; // 定位到插入的位置 int pos; for (pos = 0; pos < arrLen; pos++) { if (n > *(ptr + pos)) break; } printf("%d -> %d, ", n, pos); // 插入数据 for (int j = arrLen; j > pos; j--) { *(ptr + j) = *(ptr + j - 1); } *(ptr + pos) = n; arrLen++; // 可视化输出 for (int i = 0; i < arrLen; i++) printf("%d ", *(ptr + i)); printf("\n"); } return 0; } ``` ## 练习:动态内存分配(四道) 1. 输入数组大小n,然后动态分配内存,之后输入n个数给动态数组,找其中的众数输出,最后,释放动态内存。 2. 先输入字符串,然后将字符串扩展为双字节,也就是每个字母后都加空格(其中数字不加),最后将字符串输出。 3. 编程实现多个字符串连接功能,具体要求:先定义字符指针,赋值为NULL,然后开始分别输入3个字符串,每次输入后将其链接到定义好的指针上,形成新字符串,最后输出结果,释放内存。 4. 输入一个长字符串,然后输入一个长字符串中的某个存在的单词和新单词,并用新单词替换存在的单词(也就是word中的替换功能)。 5. 第五题不适合练习,跳过 完整代码如下所示。 ```C++ #include #include #include #include int main() { while (true) { int n; printf("请输入题号:"); scanf_s("%d", &n); switch (n) { case 1: { /* * 1. 输入数组大小n,动态分配内存,之后输入n个数,找其中的众数 */ int n; printf("请输入动态数组大小n:\n"); scanf_s("%d", &n); // 分配内存 int* arr = new int[n]; // 赋值 srand(time(0)); for (int i = 0; i < n; i++) { arr[i] = rand() % 100; } // 寻找众数并输出:排序后连续出现次数最大的那个数就是众数 // 1. 冒泡排序 for (int i = 0; i < n; i++) { for (int j = i + 1; j < n; j++) { if (arr[j] > arr[i]) { int t = arr[j]; arr[j] = arr[i]; arr[i] = t; } } } // 2. 查找连续次数最多的数 int mode = arr[0]; // 众数 int max = 1; // 记录最大的连续次数 int cur = 1; // 记录当前连续次数 for (int i = 0; i < n - 1; i++) { if (arr[i] == arr[i + 1]) { cur++; } else { if (cur > max) { max = cur; mode = arr[i]; } cur = 1; } } // 打印输出 for (int i = 0; i < n; i++) printf("%d ", arr[i]); printf("\n"); if (max == 1) { printf("没有众数\n"); } else { printf("众数是:%d,出现的次数为:%d\n", mode, max); } // 释放内存 delete[] arr; break; } case 2: { /* * 2. 先输入字符串,然后将字符串扩展为双字节。每个字母后面加空格,最后将字符串输出 */ char str[] = "String \"Hello World\" has 2 words."; // 计算需要分配的内存数量 int n = 0; for (int i = 0; str[i] != '\0'; i++) { if ((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z')) n += 2; else n += 1; } char* wide_str = new char[n + 1]; // 扩展字符串 int cur = 0; // 用于记录光标的当前位置 for (int i = 0; str[i] != '\0'; i++) { wide_str[cur++] = str[i]; if ((str[i] >= 'A' && str[i] <= 'Z') || (str[i] >= 'a' && str[i] <= 'z')) { wide_str[cur++] = ' '; } } wide_str[cur] = '\0'; printf("双字节字符串为:%s\n", wide_str); delete[] wide_str; break; } case 3: { // 3. 多字符串连接 char str1[] = "Hello,"; char str2[] = "World!"; char str3[] = "-20211121"; //// 统计合并后的字符串长度 //int n = strlen(str1) + strlen(str2) + strlen(str3); //char* str = new char[n + 1]; //// 赋值 //int cur = 0; //for (int i = 0; str1[i] != '\0'; i++) str[cur++] = str1[i]; //for (int i = 0; str2[i] != '\0'; i++) str[cur++] = str2[i]; //for (int i = 0; str3[i] != '\0'; i++) str[cur++] = str3[i]; //str[cur] = '\0'; //printf("%s + %s + %s = %s\n", str1, str2, str3, str); //delete[] str; n = strlen(str1); // 为第一个字符串分配内存空间 char* str = (char*)malloc(n * sizeof(char)); int cur = 0; for (int i = 0; str1[i] != '\0'; i++) str[cur++] = str1[i]; // 重新分配内存空间 n += strlen(str2); str = (char*)realloc(str, n * sizeof(char)); for (int i = 0; str2[i] != '\0'; i++) str[cur++] = str2[i]; n += strlen(str3); str = (char*)realloc(str, n * sizeof(char)); for (int i = 0; str3[i] != '\0'; i++) str[cur++] = str3[i]; // 补0 str = (char*)realloc(str, (n + 1) * sizeof(char)); str[cur] = '\0'; // 输出 printf("%s + %s + %s = %s\n", str1, str2, str3, str); // 释放内存 free(str); break; } case 4: { /* * 4. 输入一个长字符串,一个已存在的单词和一个新单词,实现单词替换 */ char str[] = "This is a string named \"Hello World\"!"; char word[] = "Helloo"; char replace[] = "Gooddddddd"; printf("原字符串:%s\n", str); // 查找字符串 int len = strlen(str); int wordLen = strlen(word); int replLen = strlen(replace); int pos = -1; for (int i = 0; str[i] != '\0'; i++) { int j = 0; for (; j < wordLen; j++) if (word[j] != str[i + j]) break; // 表示找到 if (wordLen == j) { pos = i; break; } } // 没找着 if (pos == -1) { printf("未发生更改:%s\n", str); } else { // 构建新的字符串 int nLen = len - wordLen + replLen; char* nStr = new char[nLen + 1]; int cur = 0; // 遍历原字符串 for (int i = 0; i < len; i++) { if (i == pos) { for (int j = 0; j < replLen; j++) { nStr[cur++] = replace[j]; } i += wordLen - 1; } else { nStr[cur++] = str[i]; } } nStr[cur] = '\0'; printf("找到关键词,替换后的字符串为:%s\n", nStr); delete[] nStr; } break; } case -1: { return 1; } default: break; } printf("\n"); } return 0; } ``` ## 分割子字符串 ```C++ #include #include #include int main() { //std::string str = "c:/windows/system/tmp/t.log"; //std::vector list; //int flag = 0; //for (int i = 0; str[i] != '\0'; i++) //{ // if (str[i] == '/' || str[i] == '\\') // { // list.push_back(std::string(str.begin() + flag, str.begin() + i)); // flag = i; // } //} //if (flag = opacity) { opacity += 20; // list = (char**)realloc(list, sizeof(char*) * opacity); if (char** tmp = (char**)realloc(list, sizeof(char*) * opacity)) { if (tmp != nullptr) list = tmp; else throw "内存再分配出错"; } } list[size] = substr; size++; flag = i; } } if (flag < i) { char* substr = (char*)malloc(sizeof(char) * (i - flag + 1)); for (int j = flag; j < i; j++) { substr[j - flag] = str[j]; } substr[i - flag] = '\0'; if (size >= opacity) { opacity += 20; if (char** tmp = (char**)realloc(list, sizeof(char*) * opacity)) { if (tmp != nullptr) list = tmp; else throw "内存再分配出错"; } } list[size] = substr; size++; } // output printf("%s\n", str); for (int i = 0; i < size; i++) { printf("%s\n", list[i]); } for (int i = 0; i < size; i++) { free(list[i]); } free(list); return 0; } ```