C语言常见问题

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)

法三:

  1. 右击项目名称->属性:
    在这里插入图片描述
  2. 找到配置属性 -> C/C++ -> 预处理器 ->预处理器定义 -> 编辑:
    在这里插入图片描述
  3. 添加代码_CRT_SECURE_NO_WARNINGS
    在这里插入图片描述
  4. 然后一直确认即可

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--;
	}
}

当我用不同的写法传入该函数时:

写法1char str[] = "abcdefg";
ReveseStr(str);  // 成功运行
写法2char* 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;
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

亲爱的老吉先森

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值