C语言编程实战:大家一起云刷题 - day4

在这里插入图片描述

🌈这里是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就是反转后的头节点了。


本节完…

评论 29
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值