文章目录
Q: goto语句有什么不好?
Answer:
goto语句不是天生的魔鬼,只是通常有替代它的更好的方式。使用过多的goto语句的程序会迅速退化成“垃圾代码”,因为goto语句会使控制跳来跳去。垃圾代码时非常难于理解和修改的。
由于goto语句可以既可以往前跳,也可以往后跳,所以使得程序非常难于阅读和理解。(break语句和continue语句只是往前跳)含有goto语句的程序经常要求阅读者来回跳转以理解代码的控制流。
goto语句使程序难于修改,因为它可能会使某段代码用于多种不同的目的。例如,对于有标号的语句,既可以在执行完其前一条语句后到达,也可以通过多条goto语句中的一条到达。
Q: C4996: This function or variable may be unsafe
在使用一些比较古老的代码或函数时,可能会无法通过现代编译器的检查,出现以下报错:
错误 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.
Answer:
法一:
在文件头部
,添加宏定义:
#define _CRT_SECURE_NO_WARNINGS
这行代码必须放在该文件所有代码行的最前面,否则不能起作用。
法二:
在文件头部
,添加预处理指令,屏蔽掉C4996安全性检查
#pragma warning(disable:4996)
法三:
- 右击项目名称->属性:
- 找到配置属性 -> C/C++ -> 预处理器 ->预处理器定义 -> 编辑:
- 添加代码
_CRT_SECURE_NO_WARNINGS
- 然后一直确认即可
Q: 使用malloc分配动态数组引发了异常:写入访问权限冲突。
出现异常代码如下:
int Fib1(int n) {
if (n == 1 || n == 2) return 1;
int* dp = (int*)malloc(sizeof(int) * n);
if (!dp) return -1;
dp[0] = 1; // 报错:引发了异常: 写入访问权限冲突。 **dp** 是 0x27A7DF0。
dp[1] = 1;
for (int i = 2; i < n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
int res = dp[n - 1];
free(dp);
return res;
}
具体报错信息显示为:
Answer:
添加头文件<stdlib.h>
Q: 传入char* str = "abcde"
类型的参数给目标函数出现写入访问冲突
Answer:
例如:我有这么一个函数,其作用是将字符串str中的内容逆序:
void ReveseStr(char *str) {
int left = 0;
int right = strlen(str) - 1;
while (left < right) {
char temp = str[left];
str[left] = str[right];
str[right] = temp;
left++;
right--;
}
}
当我用不同的写法传入该函数时:
写法1:
char str[] = "abcdefg";
ReveseStr(str); // 成功运行
写法2:
char* str = "abcdefg";
ReveseStr(str); // 报错
写法2报错:
究其原因在于传递的字符串 char* str = "abcdefg";
这个情况下。将 “abcdefg” 赋值给了一个 char* 类型的指针,而这个字符串是一个常量字符串,它的内容存储在只读内存区域,所以不能修改其内容,因为在 ReveseStr 函数中尝试修改这个只读内存区域的内容(通过 str[left] = str[right];),就会导致访问冲突(segmentation fault),即看到的异常错误。可通过如下方式解决:
char *str = malloc(7 * sizeof(char)); // 动态分配空间
strcpy(str, "123456"); // 复制字符串到可修改的内存
ReveseStr(str); // 可以成功修改
free(str); // 使用完后释放内存
这样可以确保 char* 指针指向可写的内存。
Q: #include<stdio.h> 和 #include"stdio.h"的区别
Answer:
#include<stdio.h>
方式,编译器从存放C编译系统的子目录中去找要包含的头文件,这称为标准方式。而#inlcude"stdio.h"
方式,在编译时,编译器优先在用户的当前目录(一般是用户存放源程序文件的子目录)中寻找要包含的头文件,若找不到,再按标准方式查找。
Q: 关于值传参和地址传参的问题:
Answer:
有一个链表的定义为:
typedef struct node
{
int data;
struct node* next;
}LinkNode, * LinkList;
这是一个对单链表进行插入排序的代码,请判断有什么问题:
void InsertSortList(LinkList L) {
LinkNode* p = L;
// 空链表直接返回
if (p == NULL) return;
// 只有一个节点直接返回不用排序
if (p->next == NULL) return;
// q指向待排序节点链
LinkNode* q = p->next;
// p指向已拍下节点链,把第一个节点断开
p->next = NULL;
// pre是p的前驱节点
LinkNode* pre = NULL;
while (q) {
// q 是本轮的待插入节点,temp指向剩余的待排序节点链
LinkNode* temp = q->next;
while (p && p->data < q->data) {
pre = p;
p = p->next;
}
// pre==NULL,说明应该插入到链头位置
// 但是由于形参的写法,但由于头指针没有正确传出,插入到头部的节点丢失了
if (pre == NULL) {
q->next = L;
L = q;
}
else {
q->next = pre->next;
pre->next = q;
}
q = temp;
p = L;
pre = NULL;
}
}
在大多数情况下,这个代码没有问题,但是对这样一个链表排序后会出现下边的情况:
会发现最后一个 1 居然消失了,这是什么原因呢?
答案就出现在形式参数
上,C语言都是按值传递
的
- 该函数的参数是一个普通指针(传值传递),即形参 L 是原始链表头指针的一个拷贝。在函数内部,当执行:
if (!pre) {
q->next = L;
L = q;
}
这段代码块时,这种操作虽然更新了 L(使新插入的节点成为链表头),但这种更新只作用于函数内部的局部变量 L,不会传回调用者。因此,当待插入的节点需要插入到链表头时,新头指针更新了,但外部仍然指向原来的头节点,从而导致排序后链表中少掉一个节点(例如,重复的那个 1)
修改方法->将函数参数修改为指向指针的指针(即 LinkList* 或 LNode**),这样在函数内部修改头指针时就能影响到调用者:
void InsertSortList(LinkList* L) {
LinkNode* p = *L;
// 空链表或只有一个节点直接返回
if (p == NULL || p->next == NULL) return;
// q指向待排序节点链
LinkNode* q = p->next;
// 断开已排序链表和待排序链表
p->next = NULL;
// pre是p的前驱节点
LinkNode* pre = NULL;
while (q) {
// q 是本轮的待插入节点,temp指向剩余的待排序节点链
LinkNode* temp = q->next;
// 在已排序链表中找到插入位置
p = *L;
pre = NULL;
while (p && p->data < q->data) {
pre = p;
p = p->next;
}
// 插入到链表头部
if (pre == NULL) {
q->next = *L;
*L = q;
}
// 插入到链表中间或尾部
else {
q->next = pre->next;
pre->next = q;
}
// 继续处理下一个待排序节点
q = temp;
}
}