标签 - C

? C ?    2020-03-15 11:02:03    516    0    0

0x01 前言

圆满完成了人生中的第一个C项目,项目如下:

title

相当于模拟一个内存管理系统。写项目的过程中不断地调试、也遇到了很多问题,记录几个调过的 bug。

0x02 通过函数改变全局变量的值

问题代码及输出:

title

title

问题分析:

我把 piece_end 定义为一个全局变量。现在的问题是,只是在函数的作用域内改变了 piece_end 的值,但是我实际想实现的是:通过函数在整个程序的作用域内改变 piece_end 变量的值。

上面的代码中,我把 piece_end 作为形参传入、自然只能在函数作用域内改变此变量的值。

解决方案是不把 piece_end 作为形参,只是通过函数改变此变量的值。

改正后的代码及输出:

title

title

于是此问题得到了解决。

0x03 通过插入 printf debug 异常退出

另一个 bug 是:我老遇到「程序卡住了」的问题。

比如下图中,我输入了新的/将要修改为的字符串 ear,程序自动计算出其长度为3,然后程序就卡住了:

title

程序是这样写的:

title

遇到这种问题的时候,可以这样调试:

title

最简单的办法是:

在红圈的地方插入一些 printf 语句。 看看哪些语句执行了,哪些些没执行。然后在对相应的位置进行单步调试。这样不用关心代码逻辑 直接定位 bug 大致位置。

单步调试一般是看 watch、memory 窗口 看相应的变量是否正常。

可以从上图中看出,主要是在一些 for、while 循环的地方插入 printf 语句,查看程序运行流程是走了哪个分支。

通过这样的调试方法,我定位到了 bug 的位置是在下面的 for 循环中:

title

title

我这里比如num=3,b=4。运行了一次不是应该跳出来继续执行修改成功吗,但是就卡住了:

title

定位了 bug 的位置,继续单步调试:把numb添加到watch窗口观察变量的变化(或者直接 printf 打印里面变量的值、寻找是否有异常值)。

title

结果发现:piece_end 的值为异常值,所以 piece[piece_end] 访问了不该访问的位置,发生数组越界,造成异常才退出(a[10]数组最多能访问a[

? C ?    2020-03-13 15:08:33    566    0    0

0x01 报错

本文主要记录编程过程中遇到的一个小报错,相信也是其他初学者会碰到的问题。

报错是:

C2371 重定义:不同的基类型

title

0x02 背景

我想在 demo 主方法中加一个 a or b or c or d 的单选方法,来进行增删查改不同的操作。可是出现了红框中的一些报错:

title

这个 get_first() 方法是获取用户输入的首字母的:

title

单独测试 choice.c 是没问题的。

0x03 问题解决

原因是:

报「未定义」警告的,应该是没把头文件包含进去。
报「重定义」警告的,应该是有多个相同函数名的函数。

根据报错,我给 choice.c 源文件通过预定义处理器包含了各种头文件就解决了此问题:

title

? C ?    2020-03-10 19:51:37    760    0    0

0x01 问号

事情的起因是我在写一个程序的时候,遇到了一件怪事:

title

就是,我打印 page 数组里的元素,除了第一个字符是正常输出,其他的都是?

但是我这个 page 数组是通过 get_strings()这个函数来赋值的,我在 get_strings() 这个函数里面打印了这个 page 数组,完全正常。这说明赋值成功:

title

那为什么我在 main 函数里面打出来就是 ?

这让我想起了一句歌词:

小问号,你是否有很多的朋友。为什么、别人在那看漫画,我却要学画画,以后长大了别人都抄我写的歌。

0x02 解决

经过咨询大佬,我很快得到了问题的原因:

在 main 函数中,声明 page 数组的时候,我一不小心把数组元素类型指定为了 int,而 page 数组作为用户输入的字符串其实是 char 类型数组。

大佬说:看下内存对应的具体数据是啥?猜测是不可显示字符。

我说:应该不是不可显示字符。因为通过 get_strings 方法可以打印出来剩余字符,就是普通的字母而已。

大佬继续进行了解释:%c 输出问号,就表示是不可在屏幕上显示的字符。main 函数中的 page[] 数组是 int 数组,get_strings() 中的是 char 数组。1 int 占 4 bytes,1 char 占 1 byte,位置不一样,导致在对应的 int 中的 page[1] 其实不是你输入的数据。

总之,就是 page 数组的类型不一致导致的这个 bug,当我把 main 函数中的 page 数组的类型改为 char,问题就圆满解决了:

title

忽略这生涩稚嫩的代码......

0x03 看内存

找大佬请教了如何通过看内存 debug,大概明白了?记录一下:

先下两个断点:一个在 get_strings() 方法处,一个在 for 循环前。

title

然后点击本地 Windows 调试器

title

打开 调试窗口内存内存1
打开 调试窗口监视监视1

现在 VS 出现了两个新窗口,一个内存窗口,一个监视窗口:

title

在监视窗口添加项 page,按下回车,就出现

? C ?    2020-03-09 17:16:22    439    0    0

本章要点:

  • 关键字:static
  • 运算符:&*(一元)
  • 如何创建并初始化数组
  • 指针、指针和数组的关系
  • 编写处理数组的函数
  • 二维数组

关于数组的基本概念

什么是数组?

数组由数据类型相同的一系列元素组成。

注:
这里的数据类型是“基本数据类型”。如:float、char、int......
普通变量可以使用的类型,数组元素都可以用。

需要使用数组时,通过声明数组告诉编译器:

  • 数组内含多少元素
  • 这些元素的类型

编译器根据这些信息正确地创建数组。

一些数组声明

  1. /* 一些数组声明 */
  2. int main(void)
  3. {
  4. float candy[365]; //内含365个float类型元素的数组
  5. char code[12]; /*内含12个char类型元素的数组*/
  6. int states[50]; /*内含50个int类型元素的数组*/
  7. }
  • 方括号([])表示candy、code、states都是数组。
  • 方括号中的数字表明数组中的元素个数。

访问数组中的元素

要访问数组中的元素,通过使用数组下标数(也称为索引)表示数组中的各元素。

数组的编号从0开始。

初始化数组

数组被用来储存程序需要的数据。在这种情况下,在程序一开始就初始化数组比较好。

C中初始化数组的语法

  1. int main(void)
  2. {
  3. int powers[8] = {1,2, 4,6,8 , 16,32 ,64}; /* 从ANSI C开始支持这种初始化*/
  4. }
  • 逗号和值之间可以使用空格。
  • 不支持 ANSI 的编译器会把这种形式的初始化识别为语法错误,在数组声明前加上关键字 static 可解决此问题。

使用const关键字把数组设置为只读

比如:

  1. const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
  • const 关键字声明和初始化数组。

这样把数组设置为只读之后,程序只能从数组中检索值,不能把新值写入数组。

const 使得程序在运行过程中不能修改该数组中的内容。

存储类别警告

数组

? C ?    2020-03-06 15:36:48    435    0    0

C的项目是这样组织的:

1、usehotel.c

  • 主流程控制文件,里面包含main()函数
  • 需要通过 #include包含头文件 hotel.h
  • 使用了hotel.c里面的一些函数,主要放函数调用

2、 hotel.c

  • 是一些自定义功能函数的合集
  • 主要是放具体函数定义的。

3、hotel.h

  • 头文件,主要放define的符号常量和hotel.c中所有的函数原型。

函数的三部曲:原型、定义、调用就这样被组织起来了。

使用的时候:

  • 把 usehotel.c和hotel.c放在一个项目下编译(IDE 前提下),这样就无需跟python一样import函数,即可相互调用。

    可以感觉到,使用vs studio 这种IDE时候,每次运行是运行了项目下所有的文件,都没有错误时候才可运行。

  • usehotel.c和hotel.c都#include "hotel.h"。

这样就把三个文件紧密的结合起来了。

usehotel.c:

title

hotel.c:

title

hotel.h:

title

? C ?    2020-03-06 15:36:39    419    0    0

本章要点

title

strlen()函数

title

不是基本数据类型的数组

title

list不是基本数据类型,这种数组在下一章会讲。

ANSI C风格的函数原型

title

函数签名

title

编译器的工作

title

定义带形式参数的函数

title

驱动程序

title

返回值可以是表达式的值

title

当返回值的类型与函数声明的类型不匹配

title

在函数中使用多个return语句

title

为什么需要函数(类型)声明

title

函数原型的位置

  • 可以放在主调函数内,也可以放在主调函数外
  • 主要要保证在函数调用之前声明

title

头文件中包含函数声明,但没有具体定义

title

ANSI C函数原型

title

函数原型和函数定义的形参名称可以不一致

title

无参数和未指定参数

title

  • 无参数需要用 void 关键字说明,而不是直接给形参那个括号留空。

函数原型的意义

title

递归

递归的定义

title

递归函数停止语句

title

尾递归的定义

title

一个尾递归函数

title

title

循环和递归的选择问题

title

编译多源代码文件的程序

title

头文件的作用

title


查找地址:&运算符

title

title

  • 所谓的主调函数就是 main() 函数。

指针

指针的定义

title

指针的使用

title

  • 变量指向指针

间接运算符:*

title

与指针相关的运算符

title

声明指针

title

title

指针是一种单独的类型,不是整数类型

title

变量:名称、地址和值

变量三属性:

  • 名称
  • 地址

指针:

  • 地址

变量:

  • 名称

title

使用指针在函数间通信

title

title

title

scanf()函数

title

传递值

title

指针的作用

title

函数签名

title

? C ?    2020-03-06 15:36:31    303    0    0

本章要点

title

本章主要介绍用于输入和输出的函数(即I/O函数)。

文件结尾

title

title

重定向输入、输出

title

重定向输入

title

组合重定向

title

title

注释

title

实现打开一个文件并显示该文件

title

小结

title

重定向是一个命令行概念。也可以借助于集成开发环境指定重定向。

注意“重定向输入、输出”的前提:

title

这里面的C程序需要先经过gcc编译成为可执行文件。

一点输入函数的技巧

title

丢弃换行符

title

title

字节流

title

菜单浏览

title

自定义get_first()函数

title

混用scanf()和getchar()的注意点

title

? C ?    2020-03-06 15:36:13    230    0    0

本章要点

title

ctype.h系列的字符函数

title

title

title

title

else与if配对

title

标记flag

title

逻辑运算符

title

#include <iso646.h>

title

求值顺序

因为&&是逻辑运算符,所以&&连接的条件,先左后右执行:

title

title

空白字符的判断

title

一些包含函数原型的头文件

title

条件运算符:?

title

title

title

循环辅助:continue和break

title

title

while和for循环中continue的区别

细品:

title

而while中:

title

break语句

title

title

title

多重选择:switch和break

switch 的用处:

title

title

title

title

丢弃一行中其他字符

title

title

一图看懂switch

title

switch和if else

title

break和continue的另一重理解

title

goto跳出嵌套循环

title

本章小结

title

? C ?    2020-03-06 15:36:10    189    0    0

本章要点

title

控制程序流!!

title

跳出循环

title

title

伪代码

title

_Bool类型

title

出口条件循环:do while

title

title

char类型数组

title

pow()函数

title

使用带返回值的函数

title

函数接口和函数实现

title

? C ?    2020-03-06 15:36:05    110    0    0

第5章:运算符、表达式和语句

本章要点

  • 关键字:while、typedef
  • 运算符:=、-、*、/、%、++、--、(类型名)
  • C语言的各种运算符,包括用于普通数学运算的运算符
  • 运算符优先级以及语句、表达式的含义
  • while循环
  • 复合语句、自动类型转换和强制类型转换
  • 如何编写带有参数的函数

基本运算符

title

除法运算符:/

title

typedef

title

负数求模

title

递增运算符:++

title

不要自作聪明

title

title

每个表达式都有一个值

title

语句

title

总结:表达式和语句

title

类型转换

title

带参数的函数

title

下图是函数原型:

title

title

自动类型转换

title