
🌈这里是say-fall分享,感兴趣欢迎三连与评论区留言
🔥专栏:《C语言从零开始到精通》《C语言编程实战》《数据结构与算法》《小游戏与项目》
💪格言:做好你自己,你才能吸引更多人,并与他们共赢,这才是你最好的成长方式。
前言:
今天给大家带来一些C语言相关的题目和易错点,都是平时容易踩坑或者容易理解偏差的地方,一起来看看吧。
正文:
1. 输出什么
- 下面这段代码运行后会输出什么呢?
#include <stdio.h>
int i;
int main()
{
i--;
if (i > sizeof(i))
{
printf(">\n");
}
else
{
printf("<\n");
}
return 0;
}
估计不少小伙伴会疑惑,这里的i没初始化啊,值不是随机的吗?其实不然,全局变量如果没给初始值,编译器会默认把它初始化为0。所以i一开始是0,执行i–之后就变成了-1。
按常理说,i是int类型,sizeof(i)在32位环境下是4,-1肯定小于4,应该输出<。但这里有个坑:sizeof的返回值是无符号整数类型。当有符号数和无符号数比较时,编译器会把有符号数转换成无符号数。-1转换成无符号整数是个非常大的数,肯定比4大,所以最终会输出>。
2. 输出什么
- 再看这个代码,结果又是什么呢?提示一下,指针p是short*类型,每次移动只会跳过两个字节。
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5};
short *p = (short*)arr;
int i = 0;
for(i=0; i<4; i++)
{
*(p+i) = 0;
}
for(i=0; i<5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
我们知道,int类型在内存中占4个字节,short占2个字节。数组arr在内存中存储时,每个元素都是4个字节(假设是小端存储):
0x00ECFBF4: 01 00 00 00 // 1
0x00ECFBF8: 02 00 00 00 // 2
0x00ECFBFC: 03 00 00 00 // 3
0x00ECFC00: 04 00 00 00 // 4
0x00ECFC04: 05 00 00 00 // 5
指针p每次移动i个位置,就会指向第i个short类型的位置。循环4次,会把前4个short位置的值设为0,也就是覆盖了前两个int元素的所有字节(每个int有2个short)。所以修改后数组变成:
0x00ECFBF4: 00 00 00 00 // 0
0x00ECFBF8: 00 00 00 00 // 0
0x00ECFBFC: 03 00 00 00 // 3(没被修改)
0x00ECFC00: 04 00 00 00 // 4
0x00ECFC04: 05 00 00 00 // 5
最后打印出来的结果就是:0 0 3 4 5。
3. 两数之和
这是个经典的算法题:给定一个整数数组nums和目标值target,找出数组中和为target的两个整数,返回它们的下标。
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
int* twoSum(int* nums, int numsSize, int target, int* returnSize)
{
*returnSize = 2; // 告诉调用者返回的数组大小是2
// 用两层循环遍历所有可能的两个数组合
for (int i = 0; i < numsSize; i++)
{
for (int j = i + 1; j < numsSize; j++)
{
if (*(nums + i) + *(nums + j) == target)
{
// 找到符合条件的就分配内存存下标并返回
int* result = (int*)malloc(2 * sizeof(int));
result[0] = i;
result[1] = j;
return result;
}
}
}
return NULL; // 没找到就返回空
}
思路很直接,就是暴力枚举,把每个数和它后面的数都加一遍,看是不是等于目标值。虽然效率不是最高的,但胜在简单易懂,适合入门。
4. 回文数
- 判断一个整数是不是回文数,回文数就是正着读和倒着读都一样的数,比如121、1331。
bool isPalindrome(int x)
{
// 负数肯定不是回文数,末尾是0但本身不是0的也不是(比如120倒过来是21)
if(x<0 || (x%10 == 0 && x!=0))
{
return false;
}
// 反转数字的后半部分,和前半部分比较
else
{
int y = 0;
while(y <= x)
{
y = 10 * y + x%10; // 取出x的最后一位加到y后面
x = x/10; // 去掉x的最后一位
}
// 如果x是偶数位,y应该和x相等;如果是奇数位,y/10应该和x相等
if(x == y || x == y/10)
{
return true;
}
else
{
return false;
}
}
}
这里的技巧是不用把整个数都反转,反转一半就够了。比如12321,反转到y=123,x=12时停下,这时y/10=12和x相等,就是回文数。
5. 移除链表元素
- 给定链表的头节点head和整数val,删除链表中所有值等于val的节点,返回新的头节点。
struct ListNode* removeElements(struct ListNode* head, int val)
{
typedef struct ListNode SLTNode;
if(head == NULL) // 链表为空直接返回
{
return NULL;
}
else
{
// 先处理头节点可能就是要删除的情况
while (head != NULL && head->val == val)
{
SLTNode* tmp = head; // 保存要删除的节点
head = head->next; // 头节点后移
free(tmp); // 释放内存
tmp = NULL;
}
// 处理中间的节点
SLTNode* pcur = head;
while(pcur != NULL && pcur->next != NULL)
{
if(pcur->next->val == val) // 下一个节点要删除
{
SLTNode* next = pcur->next;
pcur->next = pcur->next->next; // 跳过要删除的节点
free(next); // 释放
next = NULL;
}
else // 下一个节点不用删,就往后移
{
pcur = pcur->next;
}
}
}
return head;
}
处理链表删除时,头节点比较特殊,因为删除头节点后新的头节点会变,所以要单独处理。中间的节点就好办了,通过前驱节点来跳过要删除的节点就行,记得要释放内存哦。
6. 指针易错点
int* p1, p2, p3;
int* pp1, * pp2, * pp3;
- 这两行代码看起来差不多,但区别可大了。在C语言里,
*这个符号是和后面的变量名绑定的,不是和前面的类型绑定的。
所以第一行int* p1, p2, p3;里,只有p1是int类型的指针,p2和p3都是普通的int变量。很多新手会以为这一行定义了三个指针,其实不是的。
第二行int* pp1, * pp2, * pp3;才是正确定义三个int指针的方式,每个变量名前面都得加个*。
为了避免 confusion,我个人推荐一行只定义一个指针,比如:
int* p1;
int* p2;
int* p3;
7. 反转链表
- 给定单链表的头节点head,反转这个链表并返回新的头节点。
typedef struct ListNode SLTNode;
struct ListNode* reverseList(struct ListNode* head)
{
if(head == NULL) // 空链表直接返回
return NULL;
// 用三个指针来记录当前、前一个和后一个节点
SLTNode* n1,*n2,*n3;
n1 = NULL,n2 = head,n3 = head->next;
while(n2) // 当n2不为空时继续反转
{
n2->next = n1; // 把当前节点的next指向前一个节点
// 三个指针都往后移一位
n1 = n2;
n2 = n3;
if(n3) // 防止n3为空时访问next
{
n3 = n3->next;
}
}
return n1; // 循环结束时n1就是新的头节点
}
反转链表的关键就是改变每个节点的next指针方向,让它指向前面的节点。这里用n1、n2、n3三个指针分别记录前一个、当前和后一个节点,一步步往后移,直到把整个链表都反转过来。最后n2会变成空,n1就是反转后的头节点了。
本节完…

975





