【VS调试教学】数据结构部分的学习建议:画图 + 写代码 + 调试(二)

简介: ❓ 刷题的时候我打死都想不到怎么办?💡 多刷题!题目刷多了思路就开开阔了,自然就能想到了。❓ 我有解题的思路,但是我写代码的时候很困难怎么办?💡 画图!通过画图可以更好地把思路转换成代码。❓ 如何学好数据结构?多画图,配合着图来写代码,再加上多调试!画图可以使用Windows自带画图,也可以用笔在草稿纸上画。刚开始的时候不管出没出问题,都建议调试一下的(力扣上要钱,直接使用搬到VS上慢慢调,爽调!)因为调试不仅仅是帮助我们分析程序找到错误的,也可以让我们去观察和理解程序。调试才是硬技能!

五、如何写出易于调试的代码(模拟实现strcpy)


0x00 优秀的代码

7dcc472d2b3a85e4a43dbf0b6c3e0647_20210608143601705.png


0x01 常见的coding技巧

8264b7bff81f43415a166461561b146b_20210608143910758.png


0x02 strcpy函数介绍

c5004b99ee5f8801e3a3534cb12e3716_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


/* strcpy: 字符串拷贝 */
#include <stdio.h>
#include <string.h>
int main()
{
  char arr1[20] = "xxxxxxxxxx";
  char arr2[] = "hello";
  strcpy(arr1, arr2);   // 字符串拷贝(目标字符串,源字符串)
  printf("%s\n", arr1); // hello
  return 0;
}

0x03 模拟实现strcpy

💬 示例 - 模拟实现 strcpy

e8b0a19c3ea6c7e9b93da99d0f43febf_20210608150107854.png


#include <stdio.h>
char* my_strcpy (
  char* dest, // 目标字符串
  char* src   // 源字符串
  )
{
  while (*src != '\0') {
  *dest = *src;
  dest++;
  src++;
  }
  *dest = *src; // 拷贝'\0'
}
int main()
{
  char arr1[20] = "xxxxxxxxxx";
  char arr2[] = "hello";
  my_strcpy(arr1, arr2);
  printf("%s\n", arr1); // hello
  return 0;
}


0x04 优化 - 提高代码简洁性

💬 函数部分的代码,++ 部分其实可以整合到一起:

#include <stdio.h>
char* my_strcpy (char* dest, char* src)
{
  while (*src != '\0') {
  *dest++ = *src++;
  }
  *dest = *src;
}

0x05 优化 - 修改while

2. 甚至可以把这些代码都放到 while 内:


( 利用 while 最后的判断 ,*dest++ = *src++ 正好拷走斜杠0 )


char* my_strcpy (char* dest, char* src)
{
  while (*dest++ = *src++) // 既拷贝了斜杠0,又使得循环停止
  ;
}

0x06 优化 - 防止传入空指针

❓ 如果传入空指针NULL,会产生BUG

331059f0c8feae3b0801da9e6bcedead_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

💡 解决方案:使用断言


断言是语言中常用的防御式编程方式,减少编程错误;

如果计算表达式expression值为假(0),那么向stderr打印一条错误信息,然后通过调用abort来终止程序运行;

断言被定义为宏的形式(assert(expression)),而不是函数;

9df5a00202ecc09d1bff6d356246278d_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

#include <stdio.h>
#include <assert.h>
char* my_strcpy(char* dest, char* src)
{
  assert(dest != NULL); // 断言   "dest不能等于NULL"
  assert(src != NULL);  // 断言   "src 不能等于NULL"
  while (*dest++ = *src++)
  ;
}
int main()
{
  char arr1[20] = "xxxxxxxxxx";
  char arr2[] = "hello";
  my_strcpy(arr1, NULL); // 👈 实验:传入一个NULL
  printf("%s\n", arr1);
  return 0;
}


🚩 运行结果如下:

7263de6753be84e32003a55d8611cb1a_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

0x07 关于const的使用

💬 将 num的值修改为20:

int main()
{
  int num = 10;
  int* p = &num;
  *p = 20;
  printf("%d\n", num);
  return 0;
}

🚩  20


💬 此时在 int num 前放上 const :


const 修饰变量,这个变量就被称为常变量,不能被修改,但本质上还是变量;

6f451afff04c448c8bc64d5cb84a76e3_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

但是!但是呢!!

int main()
{
    const int num = 10;
    int* p = &num;
    *p = 20;
    printf("%d\n", num);
    return 0;
}

🚩 运行结果如下

f5b4054fc4dd485101491882622d045c_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

❗ 我们希望 num 不被修改,结果还是改了,这不出乱子了吗?!


num 居然把自己的地址交给了 p,然后 *p = 20,通过 p 来修改 num 的值,不讲武德!


💡 解决方案:只需要在 int* p 前面加上一个 const,此时 *p 就没用了

int main()
{
    const int num = 10;
    const int* p = &num; 
// 如果放在 * 左边,修饰的是 *p,表示指针指向的内容,是不能通过指针来改变的
    *p = 20; // ❌ 不可修改
    printf("%d\n", num);
    return 0;
}

🚩 运行结果如下:

111428aa064696d0cff5ffafdfac55cf_20210609103724499.png

🔑 解释:const 修饰指针变量的时候,const 如果放在 * 左边,修饰的是 *p,表示指针指向的内容是不能通过指针来改变的;


❓ 如果我们再加一个变量 n = 100, 我们不&num,我们&n,可不可以?

int main()
{
    const int num = 10;
    int n = 100;
    const int* p = &num;
    *p = 20 // ❌ 不能修改
    p = &n; // ✅ 但是指针变量的本身是可以修改的
    printf("%d\n", num);
    return 0;
}


🔑 可以,p 虽然不能改变 num,但是 p 可以改变指向,修改 p 变量的值


❓ 那把 const 放在 * 右边呢?


int main()
{
    const int num = 10;
    int n = 100;
    int* const  p = &num;
// 如果放在 * 右边,修饰的是指针变量p,表示的指针变量不能被改变
// 但是指针指向的内容,可以被改变
    p = 20; // ✅ 可以修改
    p = &n; // ❌ 不能修改
    printf("%d\n", num);
    return 0;
}


🔑 此时指针指向的内容可以修改,但是指针变量


❓ 如果两边都放 const :

int main()
{
    const int num = 10;
    const int* const p = &num;
    int n = 100;
    *p = 20; // ❌ 不能修改
    p = &n;  // ❌ 不能修改
    printf("%d\n", num);
    return 0;
}

0x08 优化 - 提高代码健壮性(加入const)

e9c902f4e41373ac601d265baa7a057e_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png

💡 为了防止两个变量前后顺序写反,我们可以利用 const 常量,给自己“设定规矩”,这样一来,当我们写反的时候, 因为是常量的原因,不可以被解引用修改,从而报错,容易发现问题之所在!

char* my_strcpy (char* dest, const char* src)
{
  assert(dest != NULL);
  assert(src != NULL);
  // while(*src++ = *dest) 👈 防止写反,加一个const
  while (*dest++ = *src++)
  ;
}

可以无形的防止你写出 while(*src++ = *dest) ,即使你写错了,编译器也会报错(语法错误);


📌 注意事项:按照逻辑加 const,不要随便加const(比如在dest前也加个const);


0x09 最终优化 - 使其支持链式访问

💬 实现返回目标空间的起始位置

#include <stdio.h>
#include <assert.h>
char* my_strcpy (char* dest,const char* src)
{
  char* ret = dest; // 在刚开始的时候记录一下dest
  assert(dest != NULL);
  assert(src != NULL);
  while (*dest++ = *src++)
  ;
  return ret; // 最后返回dest
}
int main()
{
  char arr1[20] = "xxxxxxxxxx";
  char arr2[] = "hello";
  printf("%s\n", my_strcpy(arr1, arr2)); // 链式访问
  return 0;
}

0x0A 库函数写法

/***
*char *strcpy(dst, src) - copy one string over another
*
*Purpose:
*       Copies the string src into the spot specified by
*       dest; assumes enough room.
*
*Entry:
*       char * dst - string over which "src" is to be copied
*       const char * src - string to be copied over "dst"
*
*Exit:
*       The address of "dst"
*
*Exceptions:
*******************************************************************************/
char * strcpy(char * dst, const char * src)
{
        char * cp = dst;
        assert(dst && src);
        while( *cp++ = *src++ )
               ;     /* Copy src over dst */
        return( dst );
}


六、模拟实现strlen函数


0x00 计数器实现

#include <stdio.h>
#include <assert.h>
int my_strlen(const char* str)
{
  assert(str);
  int count = 0;
  while (*str) {
  count++;
  str++;
  }
  return count;
}
int main()
{
  char arr[] = "abcdef";
        int len = my_strlen(arr);
  printf("%d\n", len);
  return 0;
}

0x01 指针减指针实现

#include <stdio.h>
#include <assert.h>
size_t my_strlen(const char* str)
{
  assert(str);
  const char* eos = str;
  while (*eos++);
  return(eos - str - 1);
}
int main()
{
  char arr[] = "abcdef";
  printf("%d\n", my_strlen(arr));
  return 0;
}

0x02 库函数写法

/***
*strlen.c - contains strlen() routine
*
*       Copyright (c) Microsoft Corporation. All rights reserved.
*
*Purpose:
*       strlen returns the length of a null-terminated string,
*       not including the null byte itself.
*
*******************************************************************************/
#include <cruntime.h>
#include <string.h>
#pragma function(strlen)
/***
*strlen - return the length of a null-terminated string
*
*Purpose:
*       Finds the length in bytes of the given string, not including
*       the final null character.
*
*Entry:
*       const char * str - string whose length is to be computed
*
*Exit:
*       length of the string "str", exclusive of the final null byte
*
*Exceptions:
*
*******************************************************************************/
size_t __cdecl strlen (
        const char * str
        )
{
        const char *eos = str;
        while( *eos++ ) ;
        return( eos - str - 1 );
}
size_t :无符号整型(unsigned int)
__cdecl :函数调用约定

七、编程常见的错误


0x00 编译型错误

📚 直接看错误提示信息(双击),解决问题;


或者凭借经验就可以搞定,相对来说简单;

be550b9f830c9a0187d35e2248e9bdf8_watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl81MDUwMjg2Mg==,size_16,color_FFFFFF,t_70.png


0x01 链接型错误

📚 看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。


一般是 标识符名不存在 或者 拼写错误 ;

f3310d30d5fcfe720291c18ff220a3b6_20210609142645877.png


0x02 运行时错误

📚 代码明明跑起来了,但是结果是错的;


🔑 借助调试,逐步定位问题,利用本章说的实用调试技巧解决;


0x03 建议

📜 做一个有心人,每一次遇到错误都进行自我总结,积累错误经验!


相关文章
|
17天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
17天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
17天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
17天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
17天前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
17天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
17天前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
17天前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构的基本概念;算法的基本概念、特性以及时间复杂度、空间复杂度等举例说明;【含常见的报错问题及其对应的解决方法】
|
23天前
|
存储 Java 开发者
Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效
【10月更文挑战第19天】在软件开发中,随着项目复杂度的增加,数据结构的组织和管理变得至关重要。Java中的Map接口提供了一种优雅的方式来管理数据结构,使代码更加清晰、高效。本文通过在线购物平台的案例,展示了Map在商品管理、用户管理和订单管理中的具体应用,帮助开发者告别混乱,提升代码质量。
26 1
|
17天前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
IKU达人之数据结构与算法系列学习×单双链表精题详解、数据结构、C++、排序算法、java 、动态规划 你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!