c语言学习笔记一

C基础课笔记

Day0

环境搭建

后续学习linux服务器编程建议内存8G

 

 

Day1

Hello word

1.hello world 

#include<stdio.h>

#include<stdlib.h>

void main()

{

printf("hello world !");

}

 

2.system("pause");

或者 getchar();暂停

 

 

3.代码注释

 

/* */

 

 

4.头文件:包含一些函数的定义,就像目录一样。

 

5.c语言本身不提供输入输出语句

使用输入输出,必须要调用stdio.h

 

6. 同步和 异步

同步执行等待上一个执行完成才能执行,

异步可以多个同时执行。

 

system ("");

system ("start  ");异步执行。

 

 

 

数据结构与算法

数据结构:在程序中要指定用到哪些数据以及这些数据的类型和数据的组织形式。

 

 

算法:要求计算机进行操作的步骤

 

 

 

system()运行程序

#include<stdlib.h>

system("需要运行的命令");

 

 

system 

功  能发出一个DOS命令 

用  法: int system(char *command); 

程序例

 

 

 

结束进程,关闭QQ

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 //printf("Hello, world\n");

 system("taskkill /f /im QQ.exe");

 return 0;

}

 

 

 

调用程序,同步和异步执行


同步调用:

 

 

异步调用:

 

system("start notepad");

多加了"start"

 

 

示例代码:

 

int main(int argc, char *argv[])

{

 int i = 0;

 for(;i<5;i++)

 {

 system("start notepad");

 }

 return 0;

}

 

 

 

 

system()运行命令时的转义字符

system()调用系统的程序时,在windows平台下默认的程序安装目录含有空格,system调用时无法运行,需要加上双引号。

报错:

 

 

调用IE:

system("\"C:\\Program Files\\Internet Explorer\\iexplore.exe\"");

其中 双引号需要“\”来转义。

 

 

 

 

Mac Xcode 上创建helloWorld

 

启动Xcode

 

创建一个新项目:

 

 

选择平台:

 

 

输入项目名:

 

选择创建:

 

打开源文件:

 

点击运行:

 


 

IOS上的hellWorld:

 

 

 

输入项目名:

 

 

 

 

 

选择创建:

 

 

 

选择 Main.storyboard 图形界面

 

注意选择设备

运行:

 

 

停止运行,增加桌面控件:

 

修改控件的属性:

 

运行查看

 

在控件上增加事件:

打开ViewController.h

 

 

 

在头文件中申明函数:

 

 

在源文件中写入刚定义的函数的实现体:

 

 

 

将事件和控件关联:

 

 

 

 

选择关联的事件:

 

 

 

如果想删除关联:

 

 

点击运行查看效果:

 

 

 

 

RadASM创建汇编工程

 

 

打开运行:

 

 

新建工程:

 

 

输入功能名:

 

 

选择模板:

 

 

 

 

 

 

编译并运行:

 

 

显示窗口:

 

 

 

 

 

让命令行窗口暂停的方法

1.调用system("pause");

 

有时不会在同一个窗口执行命令,那会出现多个窗口。

 

 

 

2.调用函数getchar();

会提示输入一个字符:

 

但有的时候需要多次调用,系统会将回车键作为字符获取。

 

 

 

windows平台下手动调用CL编译c文件

在windows下,安装了VS环境可以手动调用cl.exe 来编译C源码文件

命令格式:

cl  x.c

 

 

默认在源文件目录生成x.exe 和项目文件x.obj

 

 

 

Qt夸平台创建项目

在Mac下运行QT

 

 

创建项目:

 

 

创建一个纯C语言的项目:

 

输入项目名:

 

 

 

运行之后打印出hello world

 

 

在Mac下需要调用system("")运行程序时,MAC软件的目录都在/Applications目录下。

 

 

 

在mac下运行时需要加上mac 下的命令:

列子:

“open  /Applications/Stickies.app”

 

 

 

 

创建简单的MFC程序

新建简单的MFC程序:

根据向导设置:

 

选择简单的窗口程序:

 

点击完成

产看资源文件:

 

工具箱在资源视图下可以打开:

 

查看窗口:

 

 

 

增加控件,修改属性值:

双击需要增加事件的控件会自动在源文件对应增加事件:

 

 

编译运行:

最终的效果:

 

 

 

 

 

头文件

块化箱式设计——函数——函数出现

调用时的作用域问题——被调用的在前,调用的在后

嵌套调用,前面的调用后面的,后面的调用前面的——通过声明扩展作用域——声明出现

声明独立与c文件分离——头文件出现

c 语言文件至上而下执行

如果被引用的函数在引用的函数后边,将会报错,要提前申明。

在递归的情况,相互引用的情况下,也需要提前申明函数。

申明头文件是为了C文件的与申明的分离。

头文件提供了函数的申明,不包含函数的定义。

向外界提供了接口,可以将C文件编译为lib二进制文件。

extern 在其他文件定义,默认加了extern修饰

只在本文件中可见 加static 

头文件中防重复包含重复定义:

#ifndef   

#define

 

#endif

头文件和实现源码的文件应该文件名一样。

 

 

 

 

VS2013使用

新建空项目可以编译通过,如果新建源文明没有写代码编译不通过,缺少程序入口。

 

常用的快捷键:

Ctrl+C 复制

Ctrl+X 剪切

Ctrl+L 删除

Ctrl+Z 撤销

Ctrl+Y反撤销

查找 

Ctrl+F 选择单词查找

Ctrl+I 查找,向下查找

Ctrl+Shift+I 向上查找   按ESC取消。

 

插入空行:

Ctrl+Enter :在当前的上面插入一个空行。

Ctrl+Shift+Enter:在当前行的下面插入一个空行。

 

定位到行首或行尾:

Home键:定位到当前的行首。

End:键: 定位到当前行的最后行尾。

 

选择当前光标所在位置到行首:

Shift+home

选择当前光标所在位置到行尾:

Shift+End

 

调用智能提示:

Ctrl+J

或者 Alt+->

快速切换窗口

Ctrl+Tab

快速隐藏当前代码:

Ctrl+M +M

生成解决方案:

Ctrl+Shift+B

 

跳到指定的行:

1.Ctrl+G:

 

 

双击行号:

 

 

 

注释:

Ctrl+K+C

取消注释:

Ctrl+K+U

 

全屏/退出全屏:

Shift+ALT+Enter

 

定义与引用:

跳转到定义:

F12

查找所有的引用:

Shift+F12

 

查找:Ctrl+F

替换:Ctrl+H

 

调试:

F5

重新启动调试:

Ctrl+Shift+F5

执行:

Ctrl+F5

大小写切换:

转换为小写:Ctrl+U

转换为大写:Ctrl+Shift +U

 

调试 

逐语句:F11

调试逐过程:F10

设置断点: F9

 

框式选择:

Shift+Alt+方向键或者鼠标

 

 

 

 

 

 

c语言的34中运算符

 

 

 

 

C语言的32关键字

 

 

 

程序与指令

指令是对计算机进行程序控制的最小单位。

所有的指令的集合成为计算机的指令系统。 电脑是X86

 

 

程序是为了完成一项特定任务而用某种语言编写的一组指令序列。

 

 

 

 

进制的概念

 

 

 

 

Day2-

system()打开百度以及其他应用

 

 

 

 

MessageBoxA()函数

MessageBoxA(0,"窗口内容显示",“窗口标题”,num)

当num=0,只有一个按钮

num =1  

 

 

 

num=2

 

 

 

 

中文编程#define

#include<stdio.h>

#include<stdlib.h>

//auto 局部变量(自动储存)

#define  自动 auto

//break无条件退出程序最内层循环

#define  中断  break

//case   switch语句中选择项

#define  情况 case

//char单字节整型数据

#define  字符 char

//const定义不可更改的常量值

#define 恒定  const

//continue中断本次循环,并转向下一次循环

#define  继续  continue

//default switch语句中的默认选择项

#define  默认  default

//do  用于构成do.....while循环语句

#define   执行  do

//double定义双精度浮点型数据

#define  双精度实数 double

//else构成if.....else选择程序结构

#define  否则   else

//enum枚举

#define  枚举   enum

//extern在其它程序模块中说明了全局变量

#define  外部   extern

//float定义单精度浮点型数据

#define   单精度 float

//for构成for循环语句

#define   循环  for

//goto构成goto转移结构

#define  跳转 goto

//if构成if....else选择结构

#define  如果  if

//int基本整型数据

#define  整数  int

//long长整型数据

#define 长整数 long

//registerCPU内部寄存的变量

#define 寄存器  register

//return用于返回函数的返回值

#define  返回  return

//short短整型数据

#define  短整数   short

//signed有符号数

#define  有符号  signed

//sizoef计算表达式或数据类型的占用字节数

#define   求字节 sizeof

//static定义静态变量

#define 静态  static

//struct定义结构类型数据

#define  结构体  struct

//switch构成switch选择结构

#define  选择   switch

//typedef重新定义数据类型

#define 重定义  typedef

//union联合类型数据

#define  联合体 union

//unsigned定义无符号数据

#define  无符号 unsigned

//void定义无类型数据

#define  空类型 void

//volatile该变量在程序中执行中可被隐含地改变

#define  隐式  volatile

//while用于构成do...whilewhile循环结构

#define  正当  while

 

 

#define 启动函数  main

#define 打印   printf

#define  参数开始 (

#define  参数结束  )

#define  大括号开始 {

#define 大括号结束 }

#define   语句结束 ;

void  test1()

{

   system("notepad");

 

}

#define  记事本   test1();

 

在自定义的编译器开始编译时自动加载一个头文件

 

用#define 定义并替换C 语言的一些关键字,变成中文编程

 


效果图:

 

 

 

 

Visual Studio 编译器 IDE制作

创建MFC项目:

 

运行向导:

 

 

 

 

 

 

 

设置文件扩展名:

 

 

 

 

 

 

 

 

 

 

 

 

修改代码:

 

 

去掉生成文档是多加的字符:

 

修改代码:

 

 

修改图形界面文件:

 

打开界面:

 

 

 

 

增加按键并增加 事件

 

 

选择增加事件

 

在事件中增加实现代码;

 

 

 

修改被掉用的批处理:

 

 

 

 

常量的定义 以及区别(const ,define

定义常量PI的两种方式:

1.#define PI 3.14

2.const float PI 3.14

 

区别:

第一种是将PI 定义为一种符号,此时PI只是3.14的别名,在编译期间用3.14去取代 PI的值,define相当于替换。

 

第二种方式:是将PI定义成常量,但告诉编译器他的值是固定不变的,如果在程序中视图修改它的值,在编译时会报错。

 

#define定义常量有什么好处?

1.通过有意义的单词符号,可以指明该常量的意思,使得程序员在阅读代码时,减少迷惑。

 

2.需要修改常量的时候,可以只需修改一次,实现批量修改,效率高,而且准确。

 

 

 

 

变量的命名规则

标识符:

定义:程序中用于标识常量、变量、函数的字符序列。

组成:

只能由字母、数字、下划线组成、第一个字母必须是字母或下划线。

 

大小写有区别

 

 

在c语言中不能用C语言的关键字,在C语言中C++的关键字但不是C的关键字是合法的。

 

new 是C++的关键字,但不是C 的关键字,在c中是合法的标识符。

 

规则:

见名知意

不宜混淆

 

注意点:

GCC对unicode支持不好,cl对Unicode 支持比较好,在Vs2013上可以是用中文名作为标识符。

 

 

 

 

 

变量为何一定要初始化?

如果不初始化得到的数据是垃圾数据,系统在回收内存时不会对内存清理,预留了垃圾信息。

 

 

 

 

 

 

 

 

C语言中插入汇编

寄存器英文名称Register

 

寄存器CPU内部的元件,寄存器拥有非常高的读写速度,所以在寄存器之间的数据传送非常快。

 

开启调试时可以查看寄存器

 

插入汇编

_asm //_asm汇编语言

 {

    mov eax,a // a的值赋值给eax这个寄存器

    add eax,b //eax的值加b

    mov c,eax //eax赋值给c

 }

 

 

 

 

 

 

Day3-

鼠标控制

控制鼠标函数:

设置鼠标坐标:

SetCurorPos(x,y)

 

 

设置鼠标事件:

 

 

模拟键盘操作

keybd_event()

 

 

 

 

 

 

OllyDbg查看窗口信息

以管理员身份运行:

部分功能需要管理员权限。

选择插件-窗口工具

 

 

 

按shift 键 移动鼠标到需要查看的窗口就可以查看窗口信息。

 

 

可以在显示的相对应的控件下点击相对应的功能。

 

 

 

 

 

 

广告自动点击 京东版 百度版

#include <stdio.h>

#include <stdlib.h>

#include<windows.h>  //导入windows头文件

void openbaidu()  //打开百度

{

 system("\"C:\\Program Files\\Internet Explorer\\iexplore.exe\" http://www.baidu.com "); //同步打开

}

void yopenbaidu()

{

 ShellExecuteA(0, "open", "http://www.baidu.com", 0, 0, 3); //可以异步打开。

}

void searchjava()

{

 keybd_event('J', 0, 0, 0); //模拟按下J

 keybd_event('J', 0, 2, 0);

 Sleep(20);

 keybd_event('A', 0, 0, 0);

 keybd_event('A', 0, 2, 0);

 Sleep(20);

 keybd_event('V', 0, 0, 0);

 keybd_event('V', 0, 2, 0);

 Sleep(20);

 keybd_event('A', 0, 0, 0);

 keybd_event('A', 0, 2, 0);

 Sleep(20);

 keybd_event(0x0D, 0, 0, 0);

 keybd_event(0x0D, 0, 2, 0);

 Sleep(20);

 keybd_event(0x0D, 0, 0, 0);

 keybd_event(0x0D, 0, 2, 0);

 Sleep(20);

}

void click()

{

 SetCursorPos(300, 270); //设置鼠标坐标

 Sleep(20);

 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);  //设置鼠标按下

 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);         //设置鼠标松开

 Sleep(20);

 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

 SetCursorPos(270, 270);

 Sleep(20);

 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

 Sleep(20);

 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

 SetCursorPos(100, 280);

 Sleep(20);

 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

 Sleep(20);

 mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0);

 mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0);

}

void close()

{

 system("taskkill /f /im 360chrome.exe");  //杀死所有的浏览器进程。

}

void serachjingdong()   //搜索京东

{

 keybd_event('J', 0, 0, 0);

 keybd_event('J', 0, 2, 0);

 Sleep(20);

 keybd_event('I', 0, 0, 0);

 keybd_event('I', 0, 2, 0);

 Sleep(20);

 keybd_event('N', 0, 0, 0);

 keybd_event('N', 0, 2, 0);

 Sleep(20);

 keybd_event('G', 0, 0, 0);

 keybd_event('G', 0, 2, 0);

 Sleep(20);

 keybd_event('D', 0, 0, 0);

 keybd_event('D', 0, 2, 0);

 Sleep(20);

 keybd_event('O', 0, 0, 0);

 keybd_event('O', 0, 2, 0);

 Sleep(20);

 keybd_event('N', 0, 0, 0);

 keybd_event('N', 0, 2, 0);

 Sleep(20);

 keybd_event('G', 0, 0, 0);

 keybd_event('G', 0, 2, 0);

 Sleep(20);

 keybd_event(0x0D, 0, 0, 0);  //两次回车

 keybd_event(0x0D, 0, 2, 0);

 Sleep(20);

 keybd_event(0x0D, 0, 0, 0);

 keybd_event(0x0D, 0, 2, 0);

 Sleep(20);

}

void main()

{

 while (1)  //一直循环

 {

  Sleep(20);

  yopenbaidu();

  Sleep(2000);

  //search();

  serachjingdong();

  Sleep(2000);

  click();

  Sleep(3000);

  close();

 }

 

}

 

 

 

 

XueTr使用教程

作者官网:

http://www.xuetr.com/

 

冰刃在win7时代已经不支持了

除了冰刃还有wsyscheck

 

打开界面:

 

进程列表的七个栏目:

 

 

1、映像名称:进程名字

 

2、进程ID:进程的Id号码

 

3、父进程ID:说明该进程由谁创建启动的,由谁创建,那么他的父进程ID和进程ID是一样的。

 

 

 

 

4、映像路径:进程文件的具体位置
5EPROCESS:进程在系统内核的地址,熟悉点Windows内核的人,可以通过这个地址查看更多的信息,但我们的教程是面向新人的,所以可以不在意这些信息,也不影响平时使用
6、应用层访问状态:说明这个进程是否允许其它进程对自己操作,比如关闭自己,一般的杀毒软件有自我保护,会禁止其它进程对自己有任何操作
7、文件厂商:显示进程文件由那个公司出品,可以了解下该程序的出处背景,不过要知道这个文件厂商信息很容易伪造,所以只能做个参考而已不可全信

 除了这些项目,大家应该可以发现上图中我的进程项目都是用黑色和蓝色来表现的,这里不同的颜色就代表不同的意义

黑色表示这个进程的主文件是微软的,比较安全


蓝色这个进程文件不是微软出品的,可能是其他的公司或个人制作,安全性有待考证


粉红也是安全有待考证,说明这个进程中还包含了其它文件,比如有些DLL文件插入到本进程里,而这些文件没有签名,默认情况下软件不会显示粉色,需要我们右击任意进程,选择校验所有签名才行

 

 

 而如果我们选择一个微软的进程,然后它所包含的模块文件里有些文件是非微软公司出品的,就会用黄色显示出来

红色比较危险了,说明进程是隐藏的或者有其它反常表现

注,开发者的颜色说明:
         1.驱动检测到的可疑对象,隐藏服务、进程、被挂钩函数 ----> 红色
         2.文件厂商是微软的 ----> 黑色
         3.文件厂商非微软的 ----> 蓝色
         4.如果您效验了所有签名,对没有签名的模块行 ---> 粉红色
         5.进程标签下,当下方使用模块窗口时,对文件厂商是微软的进程,会检测其所有模块,如果有模块是非微软的 ----> 土黄色

  进程的检测是对病毒查杀的第一步了,这款工具对于检测到的进程操控能力很强,右击进程可以在弹出的菜单里做出各个需要的操作,相信这些文字大家都看的懂

 

  我们选择些比较实用的说下。前面提到很多病毒木马会进入到正常进程内部,起到隐藏和保护自己的目的,所以查看每个进程里包含的文件就很重要了,右击任意进程选择在下方显示模块窗口就可以了,这是后单击某个进程就可以在下方窗口显示出进程里面包含的所有文件了

 

 

不过默认中显示的并不完全,右击选择显示所有模块可以查看到那些微软出品的,软件自认为是安全的模块文件

 

 

这时候细心的网友会注意到软件最下方一栏显示的信息

 

这栏相当于对当前情况的一个汇总,意思是系统里面一共有 60个进程,隐藏的危险进程没有,有个进程自我保护不能轻易被其它程序关闭或控制。

  当前我选择的进程名字是 svchost.exe,里面有39个模块文件,其中有1个不是微软出品的。一个进程中包含了而很多模块文件是很正常的事,如果要想辨别出病毒程序就比较麻烦了,需要一定的系统知识和了解。

  假如我们判定某一个模块文件为恶意程序,可以右击它选择卸载模块、全局卸载、删除三种方式

 

分别代表将这个模块文件从改进程里除掉、将这个模块文件从所有进程里除掉、直接删除了这个文件(如果认定是病毒木马就可以采取后两个措施),不过实际操作中需要判断准确,因为如果错误卸载了正常安全的模块可能会引起系统崩溃,死机等等。而右击模块文件弹出的菜单里校验数字签名、查看模块文件属性等等都有助于判断。

 

 

不过仅供参考而已,不能作为唯一标准

  当然有时候杀毒软件会给出警报提示有某个文件是病毒,但是又无法清除,这很可能因为病毒文件插入到了其他进程之中,既然已经知道了文件名字那么要查出在哪个进程中就比较好办了,不需要一个个进程慢慢看,只需要右击选择在进程中查找模块,然后输入文件完整名字即可,找到了隐蔽之所那么就能右击来选择你所需要的操作搞定了。

 

  上面大概的介绍了下xuetr在进程管理方面主要用到的功能,而那些卸载模块、删除模块、结束进程、结束进程删除文件等等力度都十分强大,对于很多具备自我保护的杀毒软件和顽固难缠病毒都可以轻松结束和删除。当然光光操作进程也不够的我们一起认识下另外一些功能。

驱动模块

 

  这些以sysdll结尾的文件虽然不能像exe程序那样直接双击运行,但是它们的破坏力确实相当强悍,它们比一般的exe程序拥有更高的权限更容易接触系统核心,这些文件都以系统服务或者驱动的形式运行着,在进程管理查看工具里是看不到了。

  默认情况下显示的都是已经启动的驱动,但是不代表系统中就这些,还有些没有运行的驱动如果想看到就需要右击将仅显示已加载驱动取消了

 

驱动文件要想从名字上来识别是否正常可比进程辨别难多了,所以右键菜单还提供了在线搜索驱动名和在线分析两项。在线搜索就是打开google网页搜索该文件名字

 

而在线分析则打开著名的多引擎扫描网站,由网友们自己上传这个文件让37款杀毒软件扫描检测下


 

 

网友们可以根据这两种在线分析判断下驱动是否正常,对于恶意文件也可以右击采取删除操作。

  好了,下面是几个比较高深的技术,新人目前还不是和理解,所以我们简单概括下:

内核

 

  这里会显示一些驱动文件的具体位置,出处和在系统核心中地址等等,对于一些没有厂商出处的需要注意下。过滤驱动大多运用在安全软件上面,顾名思义就是过滤电脑中或网络上传输来的数据,筛选出有害的给出警报,比如文件过滤驱动可以监控文件操作等,键盘过滤驱动可以监控键盘的一些操作(这样可以实现一些快捷键效果,也有一些病毒木马利用这个来记录键盘输入)。

 

  对象劫持的一些说明:可以理解成系统中的一些文件被替换。如果您的系统出现一些劫持情况,也不必惊慌,这可能是正常现象,因为有些安装有些杀毒软件会出现这个情况。

  上图所示的“\Device\Harddisk\DR0的下层设备/驱动被劫持是机器感染TDL3+病毒后出现的,这个病毒劫持了磁盘的一些操作,但安装某些还原软件后也会有这种情况。

  如果您那里出现内核模块文件被替换,可能是驱动程序升级后未重启电脑导致的,也可能是被病毒劫持了,很可能是Window打补丁或者一些有在线升级功能的程序升级驱动模块后未重启电脑导致的,当然有些病毒也会替换系统里的一些正常驱动(比如beep.sys)来实现驱动加载,遇到这个情况需要您仔细辨认。

  如果出现“\Device\KeyboardClassX的下层设备/驱动被劫持则表示键盘相关操作可能被劫持,要注意下机器上是否安装有键盘记录器等。(注,不是所有的键盘记录行为都是病毒木马,一些安全控件也有可能有此类行为,如卡巴斯基提示PDM.Keylogger,则可能是支付宝控件

  如果出现进程对象被隐藏或者篡改则表示有进程对象被恶意篡改等。

  DPC定时器大致作用:目前有不少正常程序和rootkit会利用DPC定时器反复检查自身的Hook是否被恢复。

  GDTCPU内核模式下的一种很抽象术语,还没想到什么通俗易懂解释,不过和前面的进程查看一样,如果是红色的需要警惕了。

内核钩子

  简单地说就是拦截各种系统命令的动作,比如我们右击删除一个文件,那么就会给系统发出一个我要删除这个文件的命令。收到后windows再转给系统中专门负责删除职能的那个人,由他去具体执行,而如果病毒采用了钩子,则会监视这些操作,将所有命令先经过自己手。如果发现删除命令的对象是自己,那么就把这个命令抛弃掉,如果是其他的就继续交给那个人来执行。就有点类似于奸臣扣留所有对自己不利的奏章,欺上瞒下那种。不过并不是所有钩子都是病毒的杰作,安全软件基本都有的,如何辨别就需要对文件名字有一定了解或上网搜索下。

 

  应用层钩子意思也差不多,不过级别没有内核高

 

  网络项目对于新手来说还比较好用,可以知道哪些进程有联网行为,不过美中不足的是似乎不能在这里结束进程

 

这里的连接状态大多时候分为

LISTEN:正在监听中,随时准备连接某一个目标
SYN_SENT:客户端通过应用程序调用connect进行active open,于是客户端tcp发送一个SYN以请求建立一个连接,之后状态置为SYN_SENT
SYN_RECV:服务端应发出ACK确认客户端的 SYN,同时自己向客户端发送一个SYN,之后状态置为SYN_RECV
ESTABLISHED:代表一个打开的连接,双方可以进行或已经在数据交互了
CLOSE_WAIT:连接正在准备跟对方断开
TIME_WAIT:表示系统在等待对方的相应,让对方回复到底连还是不连
CLOSING:比较少见
CLOSED:连接被关闭

  TCPIP部分功能默认显示所有和网络连接相关函数

 

  浏览器的插件,很多软件安装完毕或者流氓软件都会在浏览器里驻足,过多的插件会给浏览器带来不稳定

 

 

  浏览器右键菜单内容显示,和很多清理工具差不多。

  SPI简单地说就是现实计算机目前所有的硬件接口和设备,右击有一个恢复lsp的选项,而这个LSP即分层服务提供商,再好的方面可以被利用编写网络监控软件,坏的方面会被木马软件利用劫持浏览器,有时候清除了病毒木马但还是无法连接网络也可能和没有恢复 lsp有关。

  Hosts文件,可能不少人都知道了

 

修改里面的内容会屏蔽一些网站,比如下面我们将网易的网址转到127.0.0.1这个ip上面,但是网易服务器的ip明显不是这个,所以打开网易就会失败,出现无法访问的现象,当然只对本机有效

 

注册表,不需要多说了吧,如果病毒禁止你使用注册表那么就可以到这里来操作,注册表里包含了而系统基本所有的设置,有关修改注册表来实现一些系统和软件设置更改的教程可以写厚厚一本书,我们这里就不多说了,大家可以自己搜索些注册表技巧看看。

文件管理器,这在小菜们手工杀毒方面也是非常重要必备的,这里可以像在我的电脑或者资源管理器那样操作文件,不过他更加强大实用,可以查看此盘里面所有的文件和文件夹,不管是否被隐藏,右击某个文件弹出的菜单也非常强大,一些正常情况下不能被删除修改的文件在这里都能轻松被消灭。

 

这里可能会有很多红色显示的文件,不一定都是危害,因为对于隐藏的文件都会用红色显示,而windows系统里有大量自带的文件和文件夹默认状态下都是被隐藏的。

右键菜单的第二个选项查看锁定情况有些网友可能比较陌生,一般无法访问或操作某个文件时,您可以用本功能看看这个文件被那些程序占用了,解锁后一般就可以访问或操作这个文件了。

 

而删除不了的文件可以试试强制删除或者添加到重启删除,当然对于一些比较顽固难产的病毒文件为了彻底杀灭可以用删除后组织文件再生或者重启替换为...来防止它死灰复燃

  另外右击某一个磁盘或文件夹可以进行搜索操作,这里的搜索功能比windows自带的强大很多,供选择的条件比较丰富

 

 

  作为手工杀毒利器,病毒木马最喜欢的开机自动运行也被分析的很干净

 

  这里分别显示了一些会自动运行的程序位置名称,等等,第二项的类型则是启动的方式,有注册表启动,也有在启动文件夹里实现开机启动的,等等。和前面查看进程一样,黑色是被认为安全的微软公司出品文件,蓝色则是其他软件公司或个人的作品。

  服务栏目里列举了本机所有的服务项目,这些也都是会开机自动启动的。

 

  当然我们早就说过病毒木马的启动并非一定要在这些位置,它们也会在后期让用户在不经意间运行病毒,比如下面的系统杂项,就包含了一些可能被篡改的内容。比如文件关联,指定了某一种格式的文件应该如何被打开,红色的是工具所不认识的方式,但也未必就是错误有害的,及时修复也可以。

 

  映像劫持之前也提到过,曾经火爆一时,现在也是需要注意。

IME输入法

  系统中安装了那些输入法,输入法之间一般通过快捷键切换,有些病毒会把自己注册成一种输入法,当用户通过快捷键切换输入法的时候,就有可能把病毒激活。我的系统里只安装了搜狗输入法,因为他不是微软的产品,所以蓝色显示。

 

系统防火墙规则

  Windows自带防火墙的过滤规则,类型里的standard app 表示这是以程序文件,而 open port表示一个端口号码。状态栏中的Enabled表示允许联网规则,Disable表示禁止联网规则。少数病毒为了顺利连接外网,也会把自己添加到系统防火墙规则里。设置为Enabled。这样这些在访问网络系统防火墙就不会报警,而是直接放行。所以查看下规则,对于有误的及时修改删除也是很有必要的

 

  不过很多网友都不用系统自带的防火墙,那样就不会看到任何有关的规则了。

  杂项里面是一些修复功能,可以用于杀毒完毕的善后或者被病毒破坏了一些设置的修复。

 

  Mbr备份恢复功能也可以试一试,因为现在也出现了破坏mbr达到重装系统都无法搞定的病毒了,不过需要注意的是重置MBRBootSector是一个相当危险的操作,稍有不慎就可能导致系统无法启动。

  最后软件还提供了一些实用的小功能,比如,禁止创建进程、禁止创建文件、禁止创建注册表等功能在杀毒的时候十分适用,可以十分有效的限制病毒行为,让你手工杀毒更快捷。这里我勾选了第一个禁止创建进程,因为双击打开一个文件程序都会产生一个进程,所以这时候我们双击任何程序都会弹出错误或没有反应。

 

 

 

 

 

 

dll注入

编译生成dll:

新建项目:

 

源代码:

MessageBoxA(0, "测试显示的内容", "测试显示的内容!", 0);

 

 

选择输出类型:

1.在项目右击属性

 

 

打开属性界面:

 

在项目默认值下的配置类型:选择动态库.dll

 

 

 

dll 文件不需要main函数 

运行注入DllInject.exe

 

 

选择需要注入的进程,点击注入

 

 

 

 

在动态连接文件:选择浏览文件

 

 

 

在源码中加入 _declspec(dllexport)

模块启动时启动的函数

 

 

 

 

 

 

进制计算

八进制—>二进制

 

分成三位一个数 ——> 对应的二进制

 

 

二进制 -->八进制

 

分成 三位  ---> 对应的八进制数

 

 

 

 

十进制--> 二进制

 

 

 

小数,浮点数 二进制十进制转换

 

 

 

 

 

 

常量与变量

变量

赋值随时可以改变

 

 

 

 

自动点击 自带浏览器 淘宝版

新建一个项目:

 

在visual c++ 中

 

MFC向导

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

点击完成即可

 

代码修改:

 

 

 

找到默认打开网页的代码:

 

 

 

 

增加一个新的事件:

 

修改为自己需要打开的网页:

 

 

打开资源视图修改界面

 

 

 

 

 

 

 

新增类别

 

 

在面板上新增按钮

 

 

 

 

在按钮上增加事件:

在需要加事件的按钮上右击选择新增事件:

 

 

 

 

 

 

 

sprintf()以及批处理for的使用


 

代码:

 

#include <stdio.h>

int main(int argc, char *argv[])

{

 int num =010;

 scanf("%d",&num);

 char str[50]="notepad";

 sprintf(str,"for /l %%i in (1,1,%d) do start calc",num);

 system(str);

 //printf("Hello, world\n");

 

 return 0;

}

 

 

 

 

sprintf(被输入的字符变量,符号的格式,符号格式中的占位符替换变量)

 

代码中的 sprintf(str,"for /l %%i in (1,1,%d) do start calc",num);

  当num传入一个数字,比如5,那么 str就为:for /l %%i in (1,1,5) do start calc 

 

 

 

for 的语法:

 

对一组文件中的每一个文件执行某个特定命令。

FOR %variable IN (set) DO command [command-parameters]

  %variable 指定一个单一字母可替换的参数。

  (set) 指定一个或一组文件。可以使用通配符。

  command 指定对每个文件执行的命令。

  command-parameters

             为特定命令指定参数或命令行开关。

在批处理程序中使用 FOR 命令时,指定变量请使用 %%variable

而不要用 %variable。变量名称是区分大小写的,所以 %i 不同于 %I.

 

 

如果启用命令扩展,则会支持下列 FOR 命令的其他格式:

FOR /D %variable IN (set) DO command [command-parameters]

    如果集(set)中包含通配符,则指定与目录名匹配,而不与文件名匹配。

FOR /R [[drive:]path] %variable IN (set) DO command [command-parameters]

    检查以 [drive:]path 为根的目录树,指向每个目录中的 FOR 语句。

    如果在 /R 后没有指定目录规范,则使用当前目录。如果集仅为一个单点(.)字符,

    则枚举该目录树。

FOR /L %variable IN (start,step,end) DO command [command-parameters]

    该集表示以增量形式从开始到结束的一个数字序列。因此,(1,1,5)将产生序列

    1 2 3 4 5,(5,-1,1)将产生序列(5 4 3 2 1)

FOR /F ["options"] %variable IN (file-set) DO command [command-parameters]

FOR /F ["options"] %variable IN ("string") DO command [command-parameters]

FOR /F ["options"] %variable IN ('command') DO command [command-parameters]

    或者,如果有 usebackq 选项:

FOR /F ["options"] %variable IN (file-set) DO command [command-parameters]

FOR /F ["options"] %variable IN ("string") DO command [command-parameters]

FOR /F ["options"] %variable IN ('command') DO command [command-parameters]

    fileset 为一个或多个文件名。继续到 fileset 中的下一个文件之前,

    每份文件都被打开、读取并经过处理。处理包括读取文件,将其分成一行行的文字,

    然后将每行解析成零或更多的符号。然后用已找到的符号字符串变量值调用 For 循环。

    以默认方式,/F 通过每个文件的每一行中分开的第一个空白符号。跳过空白行。

    你可通过指定可选 "options" 参数替代默认解析操作。这个带引号的字符串包括一个

    或多个指定不同解析选项的关键字。这些关键字为:

        eol=c - 指一个行注释字符的结尾(就一个)

        skip=n - 指在文件开始时忽略的行数。

        delims=xxx - 指分隔符集。这个替换了空格和制表符的

                          默认分隔符集。

        tokens=x,y,m-n - 指每行的哪一个符号被传递到每个迭代

                          的 for 本身。这会导致额外变量名称的分配。m-n

                          格式为一个范围。通过 nth 符号指定 mth。如果

                          符号字符串中的最后一个字符星号,

                          那么额外的变量将在最后一个符号解析之后

                          分配并接受行的保留文本。

        usebackq - 指定新语法已在下类情况中使用:

                          在作为命令执行一个后引号的字符串并且一个单

                          引号字符为文字字符串命令并允许在 file-set

                          中使用双引号扩起文件名称。

    某些范例可能有助:

FOR /F "eol=; tokens=2,3* delims=, " %i in (myfile.txt) do @echo %i %j %k

    会分析 myfile.txt 中的每一行,忽略以分号打头的那些行,将

    每行中的第二个和第三个符号传递给 for 函数体,用逗号和/或

    空格分隔符号。请注意,此 for 函数体的语句引用 %i 来

    获得第二个符号,引用 %j 来获得第三个符号,引用 %k

    来获得第三个符号后的所有剩余符号。对于带有空格的文件

    名,你需要用双引号将文件名括起来。为了用这种方式来使

    用双引号,还需要使用 usebackq 选项,否则,双引号会

    被理解成是用作定义某个要分析的字符串的。

    %i 在 for 语句中显式声明,%j 和 %k 是通过

    tokens= 选项隐式声明的。可以通过 tokens= 一行

    指定最多 26 个符号,只要不试图声明一个高于字母 "z" 或

    "Z" 的变量。请记住,FOR 变量是单一字母、分大小写和全局的变量;

    而且,不能同时使用超过 52 个。

    还可以在相邻字符串上使用 FOR /F 分析逻辑,方法是,

    用单引号将括号之间的 file-set 括起来。这样,该字符

    串会被当作一个文件中的一个单一输入行进行解析。

    最后,可以用 FOR /F 命令来分析命令的输出。方法是,将

    括号之间的 file-set 变成一个反括字符串。该字符串会

    被当作命令行,传递到一个子 CMD.EXE,其输出会被捕获到

    内存中,并被当作文件分析。如以下例子所示:

      FOR /F "usebackq delims==" %i IN (`set`) DO @echo %i

    会枚举当前环境中的环境变量名称。

另外,FOR 变量参照的替换已被增强。你现在可以使用下列

选项语法:

     %~I - 删除任何引号("),扩展 %I

     %~fI - 将 %I 扩展到一个完全合格的路径名

     %~dI - 仅将 %I 扩展到一个驱动器号

     %~pI - 仅将 %I 扩展到一个路径

     %~nI - 仅将 %I 扩展到一个文件名

     %~xI - 仅将 %I 扩展到一个文件扩展名

     %~sI - 扩展的路径只含有短名

     %~aI - 将 %I 扩展到文件的文件属性

     %~tI - 将 %I 扩展到文件的日期/时间

     %~zI - 将 %I 扩展到文件的大小

     %~$PATH:I - 查找列在路径环境变量的目录,并将 %I 扩展

                   到找到的第一个完全合格的名称。如果环境变量名

                   未被定义,或者没有找到文件,此组合键会扩展到

                   空字符串

可以组合修饰符来得到多重结果:

     %~dpI - 仅将 %I 扩展到一个驱动器号和路径

     %~nxI - 仅将 %I 扩展到一个文件名和扩展名

     %~fsI - 仅将 %I 扩展到一个带有短名的完整路径名

     %~dp$PATH:I - 搜索列在路径环境变量的目录,并将 %I 扩展

                   到找到的第一个驱动器号和路径。

     %~ftzaI - 将 %I 扩展到类似输出线路的 DIR

在以上例子中,%I 和 PATH 可用其他有效数值代替。%~ 语法

用一个有效的 FOR 变量名终止。选取类似 %I 的大写变量名

比较易读,而且避免与不分大小写的组合键混淆。

 

 

 

 

调试时查看局部变量的值

 

 

在需要查看的地方按F9加上断点

运行时就可以查看到:

 

 

 

 

 

 

Day4

格式说明符的范围

格式说明符有一定的范围,只能在一定的范围内才能解析成功

 

超出范围报错:: warning: integer overflow in expression

 

 

类型大小:  字节     1字节=8位

类型

MAC

linux

windows

ANSIC 标准

char

1

1

1

1

int

4

4

4

2

short

2

2

2

2

long

4

4

4

4

long long 

8

8

8

8

 

short min-max  : -32768   32767

int min -max : -2147483647-1    2147483647

 

 

 

 


Day5

自增自减运算符

 

--i 与i--的区别

 

--i:

只跳出四次,先自减一次再循环:

 

 

i--:

先循环再自减:

 

 

 

 

在while()执行体中的自增自减:

--i:

 

 

i--:

 

 

 

 

在循环执行体中效果一样。

 

 

执行的时间:

i++ ; i-- ;在执行完所在的语句之后执行。

测试效果:

 

 

--i,++i;

先实现自减和自加

 

 

 

 

 

 

 

 

 

dos窗口下的变色龙

 

 

 

代码:

 

#include <stdio.h>

#include <windows.h>

void main()

{

 int i = 0;

 while (i++<= 0xf)//十六进制

 {

  char str[30]; //字符串

  sprintf(str, "color %x%x", i, 15 - i);//打印颜色

  system(str); //执行指令

  system("ipconfig"); //显示IP

  Sleep(1000);//暂停1秒

  //i++;

 }

}

 

 

 

 

 

dos窗口标题显示倒计时

 

 

 

代码现实:

#include <stdio.h>

#include <windows.h>

void main()

{

 int i = 0;

 while (++i)

 {

  char str[15];

  sprintf(str, "title 第%d秒", i);

  system(str);

  Sleep(1000);

 }

}

 

 

 

 

 

自增和自减和乘除加减的优先级

先负号,如果有负号先改变符号。

然后是++ 和--

再是 *  /   %

最后+  -

 

 

 

 

逻辑或与 非

在C语言中只有短路的逻辑或与

 

短路或:||

条件1 || 条件2  如果条件 1为真,那么条件2就会被短路

 

短路与:&&

条件1 && 条件 2 如果条件1 为假,那么条件2就被短路

 

逻辑非

 

 

 

三目运算

判断?执行体1:执行体2

当条件为真 执行 执行体1,当条件为假,那么执行执行体2

 

结合方向:右向左

返回的类型由后面的类型绝定。

 

 

 

 

左值与程序实体

程序实体是内存中的一块可标识的区域,左值是左值表达式的简称,是指明一个程序实体的表达式。

 

判断一个表达式是否左值的方法是看其能否放在等号的左边。能放在赋值号的左边的表达式都是左值,他指明了一块内存区域,而赋值运算实质是改变这一区域内容的操作。

 

特列:

const 常量是左值,但不能将其放在赋值号左边。

 

 

 

 

常量与程序实体

定义常量的方式有两种:

#define PI 3.14

const PI 3.14

 

const 修饰的常量是一个程序实体,可以取内存地址。也是左值,可以放在赋值符号的左边。

 

#define 定义宏,常量,无法取地址,汇编立即数,#define 定义的常量直接存取到CPU,编译器的权限接触不到常量,#define定义的常量不是程序实体,无法对常量进行标识。

 

 

 

 

结合性

 

 

操作数:

a=a+b a和b都是操作数。

 

对比操作两边的运算符,比较优先级。

 

 /*1 + 3 - 2 / 5 * 4; 优先级依托于操作数

 4 - 2 / 5 * 4;

 4 - 0 * 4

 4 - 0;

 4;*/

 //1 + 2 - 3 + 5 - 6; 结合性

 //1 + 2-9 / 3 * 4 - 5;

 //3-9/3*4-5;

 //3-3*4-5;

 //3-12-5;

 //-5+3*6;

 //4*-1;

  //从内向外,从左向右

 ((1 + 2) - 3) * (4 / 5) - 6 * 8;

 

 

 

加密与解密

 

 

 

 

#include<stdio.h>

#include<stdlib.h>

void main()

{

 //加密主函数

 char str[13] = "i love china"; //创建一个字符串数组

 printf("%s\n", str); //打印字符串

 int i = 0;

 while (i < 13) //从0循环到13

 {

  str[i] != '\0' ? str[i] += i : 1; //判断字符有没有结束。

  i++;

 }

 printf("%s\n", str);

   //解密主函数

 {

  int i = 0;

     while (i < 13)

     {

     str[i] != '\0' ? str[i] -= i : 1; //判断字符有没有结束。

   i++;

     }

     printf("%s\n", str);

 }

 getchar();

}

 

 

 

 

数据的输入与输出

获取命令并执行

 

 

 

运行时,可以重新定向输入输出。

命令行下的重新输入符"<"或">" 箭头代表输出的方向。

 

 

 

 

 

sprintf(需输入的变量,“字符格式”,需输入的字符)

 

sprintf(str, "color %d%d", 3, 5);

 

得到 color 35

 

 

 

格式符

 

 

printf("") 不会对输出量进行类型变换,类型不对,无法输出正确的答案。

 

 

%c  可接受字符,整数,

'h'   ,104 ,0150, 0x68, '\150'

 

%d 可以输出字符对应的ASCII对应的数字。

 

%mc  c默认是从右边输出的,就是从输出字符宽度的右边开始填充。

m 为我们设置的给打印出的字符的宽度。

 

 

 

%-c  左对齐,c语言默认的是右对齐

 

 

 

%[-][m][.n]s

 

-是从左边开始填充,默认是从右边开始显示

m 定义输出的字符的长度

.n定义了截取字符的长度

 

 

 

 

 

优先级

逗号运算符与表达式

用逗号将多个表达式连接起来,又称为“顺序求职运算符”。

整个表达式的值是最后那个逗号之后表达式的值。

 

逗号表达式的值为最后的一个表达式的值。

 

 

C语言的优先级共15级:

1的优先级最高,15级的优先级最低,同等优先级运算次序由结合方向所决定。

 

优先级

运算符

名称或含义

使用形式

结合方向

说明

1

[]

数组下标

数组名[常量表达式]

左到右

 

()

圆括号

(表达式)/函数名(形参表)

 

.

成员选择(对象)

对象.成员名

 

->

成员选择(指针)

对象指针->成员名

 

2

-

负号运算符

-表达式

右到左

单目运算符

(类型)

强制类型转换

(数据类型)表达式

 

++

自增运算符

++变量名/变量名++

单目运算符

--

自减运算符

--变量名/变量名--

单目运算符

*

取值运算符

*指针变量

单目运算符

&

取地址运算符

&变量名

单目运算符

!

逻辑非运算符

!表达式

单目运算符

~

按位取反运算符

~表达式

单目运算符

sizeof

长度运算符

sizeof(表达式)

 

3

/

表达式/表达式

左到右

双目运算符

*

表达式*表达式

双目运算符

%

余数(取模)

整型表达式/整型表达式

双目运算符

4

+

表达式+表达式

左到右

双目运算符

-

表达式-表达式

双目运算符

<< 

左移

变量<<表达式

左到右

双目运算符

>> 

右移

变量>>表达式

双目运算符

6

大于

表达式>表达式

左到右

双目运算符

>=

大于等于

表达式>=表达式

双目运算符

小于

表达式<表达式

双目运算符

<=

小于等于

表达式<=表达式

双目运算符

7

==

等于

表达式==表达式

左到右

双目运算符

!=

不等于

表达式!= 表达式

双目运算符

8

&

按位与

表达式&表达式

左到右

双目运算符

9

^

按位异或

表达式^表达式

左到右

双目运算符

10

|

按位或

表达式|表达式

左到右

双目运算符

11

&&

逻辑与

表达式&&表达式

左到右

双目运算符

12

||

逻辑或

表达式||表达式

左到右

双目运算符

13

?:

条件运算符

表达式1? 表达式2:表达式3

右到左

三目运算符

14

=

赋值运算符

变量=表达式

右到左

 

/=

除后赋值

变量/=表达式

 

*=

乘后赋值

变量*=表达式

 

%=

取模后赋值

变量%=表达式

 

+=

加后赋值

变量+=表达式

 

-=

减后赋值

变量-=表达式

 

<<=

左移后赋值

变量<<=表达式

 

>>=

右移后赋值

变量>>=表达式

 

&=

按位与后赋值

变量&=表达式

 

^=

按位异或后赋值

变量^=表达式

 

|=

按位或后赋值

变量|=表达式

 

15

,

逗号运算符

表达式,表达式,

左到右

从左向右顺序运算

  

 

 

 

字符串的合并

在C中字符串不能直接通过+ & 等其他语言中常用的连词符来连接,需要通过sprintf()合并。、

sprintf(str, "%s%s", str1, str2);

 

 

 

 

 

 

外挂

在windows下不许不同的进程修改其他进程的内存。

使用dll注入进程。

 

_declspec(dllexport) 

 

 

靶子:

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>

#include<Windows.h>

#include<stdlib.h>

void main()

{

 int x = 0;

 printf("%x", &x);

 while (1)

 {

  char str[20];

  sprintf(str, "title 第%d秒", x++);

  system(str);

  printf("\n%s", str);

  Sleep(1000);

 }

}

 

dll文件:

_declspec(dllexport) void mm()

{

 int *p = (int *)0x31fe60; //p的值就是0x31fe60  将转为整型的指针,方便赋值整型值。

 

 *p = 0; //根据地址赋值

}

 

 

 

 

 

Day6

关于头文件的解答

头文件根据不同的宏变量检测不同的平台

在头文件中有判断:

 

 

有时,不同的平台调用不同的头文件

 

 

查看当前项目的lib二进制文件:

打开连接器:

所有选项:

 

 

附加依赖项:

 

 

查看当前依赖项:

 

 

 

 

 

 

关于int main() return 0;

C99中放松了要求,在VC ++6.0 在编译会出现警告,在其他的编译器上编译不会出现错误,最好是带上return 0;

 

int main( void )

{

  return 0;

}

 

}

 

 

 

试卷讲解

一、选择题(每题2分)

1. 下列C语言中运算对象必须是整型的运算符是         

A) %=        B) /      C) =        D) *=


讲解:

A)

 a%=b --> a=a%b;

报错:

测试代码:

int a =10;

float b=1.5;

a%=b;

 

“%=”非法,有操作数包含 float。

 

修正:

a%=(int)b;

 


2. 在C语言中不合法的整数是     

A)20      B)0x4001      C)08      D)0x12ed


讲解:

C)

八进制:1-7 不超过8

十六进制:1-F

 

3.以下十六进制数中不合法的是

A)oxff      B)0Xabc    C)0x11     D)0x19


讲解:

A)

 



4. 若有float x;则sizeof (x)和sizeof (float)两种描述(  )。

(A)都正确  (B)都不正确  (C)前者正确 (D)后者正确

讲解:

A)

测试代码:

char *p ="abssjkslk";

sizeof(p);为4 ,指针类型的大小为4;

 

 



5. 若给定条件表达式(M)?(a++):(a--),则其中表达式(M)      

A.和(M==0)等价               B.和(M==1)等价

C.和(M!=0)等价               D.和(M!=1)等价

讲解:

C)

测试代码:

 

 

 

6. 已定义ch为字符型变量,下列赋值语句中错误的是      

A)ch='\';      B)ch=62+3;      C)ch=NULL;      D)ch='\xaa';

讲解:

A)

ASCII码表 用一个字节存储。 0-127 为表1范围,128-255为表二,特殊字符的表。

"\"为转移字符。

ch='\' 应该为ch ='\\'

B 选项为字符 ‘A’

C 选项 Null 定义为0 在ASCII中为空,什么也没有。

 

 

D 选项

测试代码:

 

结果:170,170

 

 

7. 设有“int x=11;”则表达式(x++*1/3)的值是     

A)3     B)4     C)11     D)12

讲解:

A)

a/b 两边都是整数,那么表达式的结果将转换为整数。

 



8. 下列描述中,正确的一条是      

A)C语言的整型变量可以分为int 、short、long、unsigned int、unsigned short、unsigned long等几种类型,因此整型常量也可以分为这几种类型

B)C语言的字符数据与整型数据可以互相赋值

C)若对字符型变量的定义为:“char c;”可使用语句c=“a”对变量c赋值

D)已知x为float型,执行语句(int)x后,x为int型


讲解:

B)

 

A选项没有那么多的类型,

B选项如果超过int范围,那么会被截断。

 




9. 已定义c为字符型变量,则下列语句中正确的是         

A)c='97 '      B)c=“97 “    C)c=97        D)c=“a”


讲解:

C)

A选项

打印55

 

 


10.表达式10<20的值是_______

A)10            B)20            C)0             D)1

讲解: 返回bool值

D)



11.已知char a; int b; float c; double d; 则表达式a+b * c-d的结果为_______型。

A)char           B)int            C)float          D)double

讲解:

D)

 

 

 

12. 在C程序中,x + y是_______、x + y;是_______

A)表达式、语句                   B)表达式、表达式

C)语句、语句                     D)语句、表达式

讲解:

A)

 




13.已知int x = 23;  则printf(x++*1/3)的输出结果为_______

A)8            B)7.66            C)7.33          D)7

讲解:

D)

a ++ ,++是在语句执行完之后++

 


14. 执行下列程序片断后c的值是_______

int a = 1, b = 2, c;

c = 1.0/b*a;

A)0        B)0.5      C)1        D)2


讲解:

A)

表达式的值为0.5

赋值给整型时截断,只保留整数部分。

 


15. 若有定义:char c=’\010’;则该变量中包含的字符个数是           

A)  非法定义                        B)  1个

C)  3个                            D) 4个


讲解:

B)

char 只有一个字节

'\010'为8,在ASCII 码表中只代表一个字符。

 

 


二、填空题(每题4分)

1. printf库函数的第一个参数中的格式串是指明输出数据格式,各种数据类型对应的基本格式串是:

short、int    (1) %d  ,      long     (2)%ld  ,

float   (3) %f   (4) %e ,   double   (5) %e %lf   (6)  ,

char   (7)%c    (8)%d  ,   字符串  (9)%s  

2. scanf库函数格式串和输入项之间的关系是   对应?  

3. 阅读下述程序功能:将三位整数n的十位数的数字变为0。例如,输入三位整数为738,输出为708。请将正确答案写在横线处。

# include<stdio.h>

void main( )

{

  (1) int n=0,d2=0,d0=0; ;

printf (“输入一个三位整数:”);

scanf (“%d”,&n);

d2=   (2) n/100 ;   /* 取出百位数的数字 */

d0=   (3) n%10 ;   /* 取出个位数的数字 */

printf (“输出三位整数:%d\n”,   (4)d0+d2*100  );


}




4. 若x为单精度型变量,y为字符型变量,z为整型变量,执行如下的输入语句:
scanf (“%f %c%d”,&x,&y,&z);后,从键盘输入12.77A79A86。此时,变量x、y、z的值分别为   ①      ②    、   ③   


%f 可以识别的:

遇到其他字符停止解析

遇到A就停止解析。将由下一个%C解析。解析A。

%d 


5. 已知char c= 'A';int i=1,j;执行语句j=!c&& i++后,i和j的值分别是     
      




三、问答题(每题4分)

1. 字符型常量和字符串常量有什么区别?


讲解:


 

 

 

2. 已知int i=5,j=5;试问表达式或函数输出的值:

    (1)i++  - ++j               (2)++i  - j++

1. printf (“%d\n”,++i+(++j));  (4)printf (“%d\n”,i++ +j);

讲解: 

 

3. 程序改错

 #include <stdio.h>  void main( )

{   float f=7.12;

    char c=“c”;

    printf(“%d\n”,int(f%3));

    printf("%c",c);

}

讲解:

 

 

4. 请指出以下程序段中的错误。

#include <stdio.h>

void main ( )

{

short j,i,k;

char a,b,c;

float x, y, z;

i=30000;

j=30000;

a= '9';

b= '5';

k=a*i+b*j;  //k溢出

z=x*x+y*y; //没有初始化

}

 

 

5. 以下程序的输出是什么?

#include <stdio.h>

void main ( )

    int i;

    unsigned int j;

    i= ~0;

    j= ~0;

    printf ("i=%d  j=%d\n",i,j);

}





四、编程题(每题6分)

1. 编写程序,读入3个双精度数,求它们的平均值并保留此平均值小数点后一位数,对小数点后第二位数进行四舍五入,最后输出结果。

 

 

 

2. 编写一个简单的C程序,输出以下信息:

* * * * * * * * * * * * *

C program!

* * * * * * * * * * * * *



 



 

1. 编写一个程序,输入半径,输出其圆周长、圆面积、及圆球体积。

 

 

4. 输入三个整数,按从小到大的顺序进行输出。

 

 

5. 编写一个程序,输入一个摄氏温度,输出其对应的华氏温度。


 

 

 

 

 

 

Day7

printf高级应用(格式说明符)

#include<stdio.h>

//%d,%u,%o 如果大写%D ->D %U->U %O->O

//%C,%c都是一样

//%S-%F->什么都不输出

//%E,%X,%E指数会大写,%X十六进制的字母会大写。

//%G, %g可以大写,按照%f正常是输出,按照%e,指数为大写

//打印的时候,一一对应

输出表多了会被忽略

/控制项多了,会打印出不定值

void main7()

{

   //%g可以大写,按照%f正常是输出,按照%e,指数为大写

 printf("\n%e,%f,%g", 10000000.0, 10000000.0, 10000000.0);

 printf("\n%e,%f,%g", 100.1234567, 100.1234567, 100.1234567);

 printf("\n%e,%f,%G", 10000000.0, 10000000.0, 10000000.0);

 printf("\n%e,%f,%G", 100.1234567, 100.1234567, 100.1234567);

 getchar();

}

void main6()

{

 //printf不管你是实数或者整数,按照自己方式来解析

 // %g取%e,%f之间宽度最小的 ,%g最多6个数字

 printf("%e,%f,%g", 10000000.0, 10000000.0, 10000000.0);

 printf("\n%e,%f,%g", 100.1234567, 100.1234567, 100.1234567);

 printf("\n");

 printf("%E", 10000000.0); //E决定指数大小写

 printf("\n");

 printf("%e", 10000000.0);

 printf("\n");

 printf("%F", 10000000.0); //%f中的F不能大写,不能为空

 printf("\n");

 printf("%f", 10000000.0);

 getchar();

}

void main5()

{

 printf("%s", "gogogo");

 printf("\n");

 printf("%S", "gogogo"); //%s中的S不能大写,写了以后什么都不输出

 getchar();

}

void main4()

{

 //字符%c,%C是一样的

 printf("%c", 48);

 printf("%C", 48);

 printf("%c", 65);

 printf("%C", 65);

 getchar();

}

void main3()

{

 printf("%x", 0x1A);//%X可以大写,代表十六进制字符是大写的

 printf("\n");

 printf("%X", 0x1A);

 getchar();

}

void main2()

{

 printf("%o,%x\n", 010, 0x18);

 printf("%O", 010);

 printf("\n");

 printf("%u", 010);

 printf("\n");

 printf("%U", 010);//%o,%d,%u也不可以大写

 getchar();

}

void main1()

{

 printf("%d", 10);

 printf("\n"); //%为空,D就打印出D

 printf("%D", 10); //无符号整数%d不能大写,

 printf("\n");

 printf("%"); //空

 printf("\n");

 printf("%%"); //打印一个%号

 printf("\n");

 //字符串转换, % -《 %% d <-d

 printf("%%d",11);

 printf("\n");

   //"%%%d" - %%-》% %d-映射11

 printf("%%%d", 11);

 getchar();

}

 

 

 

printf 转换失败输出

 

printf不支持类型之间的转换。

尝试将Int类型转换为float 将输出0.000000

 

尝试将float类型转换为int 时,输出0

 

 

 

 

 

第三章节总结

1.  C 语言中没有bool类型

非0为真,0为假:

 

 

宽字符:

国际化 ,

 

一个汉字占用2个字节

 

 

有符号无符号最大差别是最高位是否符号位。

 

 

 

 

第四章简介

主要的语句类型:

 

 

 

 

 

算法的流程图表示:

 

 

 

 

 

if 语句在没有大括号的情况下,默认最近的一句语句。

 

#define 整数,可以用来实现对比的

 

 

 

 

字符检测

#include<stdio.h>

#include<stdlib.h>

void main()

{

 while (1)

 {

  char ch = getchar();

  getchar();//填空回车

  if (ch >= 'A' && ch <= 'Z')

  {

   printf("\n大写字母");

  }

  else if (ch >= 'a' && ch <= 'z')

  {

   printf("\n小写字母");

  }

  else if (ch >= '0' && ch <= '9')

  {

   printf("\n数字");

  }

  else

  {

   printf("\n其他字符");

  }

 }

}

 

 

 

 

分支语句 解一元二次方程

 

 

一元二次方程的解的情况有如下的几种可能:

 

 

 

 

分析:

 

 

 

 

 

 

 

代码实现:

 

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>

#include<stdlib.h>

#include<math.h>

void main()

{

 double a, b, c;

 scanf("%lf%lf%lf", &a, &b, &c);

 printf("%lf*x*x+%lf*x+%lf=0\n", a, b, c);

 if (a) //a!=0

 {

  printf("一元二次方程");

  double m = b*b - 4 * a*c;

  double n = -1 * b / 2 / a;

  if (m == 0)

  {

   printf("两个相等的实根x1=x2=%lf", n);

  }

  else if (m>0)

  {

   printf("两个不等的实根x1=%lf,x2=%lf", n + sqrt(m) / 2 / a,

    n - sqrt(m) / 2 / a);

  }

  else

  {

   printf("两个不等的虚根%lf+%lfi,%lf-%lfi",

    n, sqrt(-1 * m) / 2 / a,

    n, sqrt(-1 * m) / 2 / a

    );

  }

 }

 else

 {

  printf("非一元二次方程");

  if (b)

  {

   printf("一元一次X=%lf",-1*c/b);

  }

  else

  {

   if (c)

   {

    printf("无解");

   }

   else

   {

    printf("X为任意值");

   }

  }

 }

 system("pause");

}

 

 

 

 

循环语句简介以及goto

 

 

CCC:  

goto CCC;

 

 

 

 

scanf函数讲解

获取地址:

scanf("%d", &x);

 

需要按照格式输入:

必须按照sacnf中的正确的格式输入,如果格式不对,那么将得不到正确的结果。

显示结果:

 

 

 

测试:格式中有逗号和没有逗号:

代码:

 

 

 

没有输入逗号:

 

 

 

输入逗号:

 

 

测试代码:

格式符里面没有逗号:

 

 

输入时,用逗号分隔将出现错我的结果:

 

输入时,不用逗号分隔,将得到正确的结果

 

 

 

 

 

对于double类型的输入必须使用%lf格式符:

 

 

 

使用%f读入将发生溢出,丢失精度。

 

输入数组,不需要加获取地址符,C默认将数组解释为指针类型。

 

 

 

间隔符:

 

 

 

截取:

读入字符,自定义获取长度:

 

 

 

格式符前*修饰符,将会跳过输入的数字:

 

 

 

x读取到1,而遇到*直接跳过 2 ,y读的3,z没有赋值,将读取内存中之前的值。

 

 

 

 

 

格式字符

 

 

%-0m.nlh

 

%表示格式说明的开始,不可缺少

-左对齐,可选,默认右对齐

0 有0表示指定的空位填0,如果缺省表示指定的空位不填

m指域宽,即对应的输出想在输出设备上所占的字符数。

n指精度。默认float的精度为6

l 对应long型

h将整型的格式符修正为short 型。

 

 

 

 

sscanf函数的使用

sscanf(str, "my name is %s", strl);

 

str 将替换 "my name is %s " 中的%s 按字符格式替换,替换的结果存入 strl .

 

scanf 是以标准输入源,而sscanf是以字符串为输入源。

扫描字符串:

 

 

 

挖掘颜色:

 

 

 

1. 常见用法。

1

2

3

charbuf[512];

sscanf("123456","%s",buf);//此处buf是数组名,它的意思是将123456以%s的形式存入buf中!

printf("%s\n",buf);

结果为:123456

2. 取指定长度的字符串。如在下例中,取最大长度为4字节的字符串。

1

2

sscanf("123456","%4s",buf);

printf("%s\n",buf);

结果为:1234

3. 取到指定字符为止的字符串。如在下例中,取遇到空格为止字符串。

1

2

sscanf("123456abcdedf","%[^]",buf);

printf("%s\n",buf);

结果为:123456

4. 取仅包含指定字符集的字符串。如在下例中,取仅包含19和小写字母的字符串。

1

2

sscanf("123456abcdedfBCDEF","%[1-9a-z]",buf);

printf("%s\n",buf);

结果为:123456abcdedf

当输入:  sscanf("123456abcdedfBCDEF","%[1-9A-Z]",buf);

1

printf("%s\n",buf);

结果为:123456

5. 取到指定字符集为止的字符串。如在下例中,取遇到大写字母为止的字符串。

1

2

sscanf("123456abcdedfBCDEF","%[^A-Z]",buf);

printf("%s\n",buf);

结果为:123456abcdedf

6、给定一个字符串iios/12DDWDFF@122,获取 和 之间的字符串,

先将 "iios/"过滤掉,再将非'@'的一串内容送到buf

1

2

sscanf("iios/12DDWDFF@122","%*[^/]/%[^@]",buf);

printf("%s\n",buf);

结果为:12DDWDFF

7、给定一个字符串“hello, world”,仅保留world

(注意:之后有一空格,%s遇空格停止,加*则是忽略第一个读到的字符串)

1

2

sscanf(“hello,world”,"%*s%s",buf);

printf("%s\n",buf);

结果为:world

%*s表示第一个匹配到的%s被过滤掉,即“hello,”被过滤了

如果没有空格则结果为NULL

 

 

 

Day8

循环结构

当型循环 和直到型循环

 

 

 

 

就do---while 为直到型。

 

先执行一次指定的循环内嵌语句,然后判断条件表达式。

 

 

 

 

多线程

多线程 _beginthread

和_beginthreadex 需要头文件 process.h

 

例子:

_beginthread(runmsg, 0, NULL);

runmsg 为需要在线程中开启的函数

 

runmsg函数:

 

void runmsg(void *p)

{  

 system("calc");

 Sleep(600000);

}

 

 

 

 

 

 

 

Day9

switch语句

switch ()

()内不能使用实数。

与break结合使用。

switch ()

{

case 条件:

break;

default:

break;

 

}

 

 

 

 

嵌套for 九九乘法表

 

嵌套for ,双层for 完全打印:

全角:

 

 

 

 

 

三角:

 

 

 

 

老师的代码在附件;

我的代码:

全角:

 

 

代码:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   printf("%d*%d=%d\t",i,j,i*j);

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

对角线:

 

 

主要代码:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   if(i==j)//如果i==j那么就打印

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

斜对角线:

 

代码:

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   if(i+j==n)//如果i+j=n那么就打印

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

双对角线:

 

 

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   if(i+j==n-1||i==j)//如果i+j=n 或者 i==j 那么就打印 ,打印双对角线。

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

右上三角:

 

 

 

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   if(i<=j)//如果i+j=n 或者 i==j 那么就打印 ,打印双对角线。

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

左下角:

 

关键代码:

if(i>=j)//如果i+j=n 或者 i==j 那么就打印 ,打印双对角线。

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

 

 

 

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   if(i>=j)//如果i+j=n 或者 i==j 那么就打印 ,打印双对角线。

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

右下三角:

 

 

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char *argv[])

{

 int n;

 scanf("%d",&n);

 for (int i=0;i<n ;i++ )

 {

  for (int j=0;j<n ;j++ )

  {

   if(i>=n-1-j)//如果i+j=n 或者 i==j 那么就打印 ,打印双对角线。

   printf("%d*%d=%d\t",i,j,i*j);

   else // 不等于的也要打印,不然只在一列。不满足条件的打印空字符。

   printf(" \t");

  }

  printf("\n");

 }

 system("pause");

 return 0;

}

 

 

 

 

 

 

 

 

 

 

 

Day10

 

Day11

三角形根据各边长求面积

设 各边长为:  a,b,c

面积为:s

p=(a+b+c)/2

 

s*s=p*(p-a)*(p-b)*(p-c);

 

 

 

 

递归

套用计算a的阶乘

 

int factrial (int a)

{

     if (a==1)

      {

         return 1;

       }

       else

       {

         a=a*factorial(a-1);

         return a;

        }

}

 

 

 

 

 

 

 

google面试题

立方体,每次只能走一步或两步,有多少可能。

 

超立方体:

 

 

平面:

 

 

F(x,y)=F(x,y-1)+F(x,y-2)+F(x-1)+F(x-2,y)

 

 

 

 

腾讯面试题

有50个台阶,一次可以走一步或者两步,问走完50个台阶,有多少个走法。

 

 

 

 

int go(int n)

{

    if(n==1)

    {

     return 1;

    }

     else if (n==2)

    {

     return 2;

     }

     else

      {

        return go(n-1)+go(n-2);

       }

}

 

 

三台阶;

除了一次可以上一步两步之外还可以上三步。

#include <stdio.h>

unsigned long int go(int n)

{

 if (n==1)

 {

  return 1;

 }

 else if (n==2)

 {

  return 2;

 }

 else if (n==3)

 {

  return 3;

 }

 else

 {

  return go(n-1)+go(n-2)+go(n-3);

 }

}

int main(int argc, char *argv[])

{

 printf("%lu\n",go(40));

 return 0;

}

 

 

 

 

 

递归的二进制转换

递归的多进制转换:

#include <stdio.h>

void changeX(int num,int bin);

int main(int argc, char *argv[])

{

 int num,bin;

    printf("输入需要转换的整数:");

 scanf("%d",&num);

 printf("输入需要转换的进制:");

    scanf("%d",&bin);

    changeX( num,bin);

 return 0;

}

void changeX(int num,int bin)

{

 if(num==0)

 {

  return;

 }

 else

 {

  int x =num%bin;

  num/=bin;

  printf("%x",x);//正序打印

 

  changeX(num,bin);

  printf("%x",x);//逆序打印

 }

}

 

书上的参考代码:

#include <stdio.h>

void to_binary(unsigned long n);

int main(int argc, char *argv[])

{

 unsigned long number;

 printf("输入一个整数(q退出):\n");

 while (scanf("%lu",&number)==1)

 {

  printf("二进制等于:");

  to_binary(number);

  putchar('\n');

  printf("再次输入一个整数(q退出)");

 }

 printf("完成\n");

 

 return 0;

}

void to_binary(unsigned long n)

{

 int r;

 r=n%2;

 if (n>=2)

 {

 // putchar('0'+r);

 

  to_binary(n/2);

 

  //putchar('0'+r);

 

 }

 

 putchar('0'+r);

 

 return;

}

 

 

 

 

 

递归实现回文

 

 

#include <stdio.h>

void showCha(char x);

int main(int argc, char *argv[])

{

 char ch;

 printf("请输入一个字母\n");

 scanf("%s",&ch);

 showCha(ch);

 return 0;

}

void showCha(char x)

{

  if (x=='a')

  {

    printf("%c",'a');

   return;

  }

  else

  {

   printf("%c",x);

   showCha( (x-1));

   printf("%c",x);

  }

}

 

 

 

 

 

Day12

sizeof strlen区别


一、sizeof
    sizeof(...)是运算符,在头文件中typedefunsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。
    它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。
    由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
    具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
    数组——编译时分配的数组空间大小;
    指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
    类型——该类型所占的空间大小;
    对象——对象的实际占用空间大小;
    函数——函数的返回类型所占的空间大小。函数的返回类型不能是void
**************

二、strlen
    strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。
    它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL
*****************

三、举例:
    eg1char arr[10] = "What?";
              int len_one = strlen(arr);
              int len_two = sizeof(arr); 
              cout << len_one << " and " << len_two << endl; 
    输出结果为:5 and 10
    点评:sizeof返回定义arr数组时,编译器为其分配的数组空间大小,不关心里面存了多少数据。strlen只关心存储的数据内容,不关心空间的大小和类型。

    eg2char * parr = new char[10];
              int len_one = strlen(parr);
              int len_two = sizeof(parr);
              int len_three = sizeof(*parr);
              cout << len_one << " and " << len_two << " and " << len_three << endl;
    输出结果:23 and 4 and 1
    点评:第一个输出结果23实际上每次运行可能不一样,这取决于parr里面存了什么(从parr[0]开始知道遇到第一个NULL结束);第二个结果实际上本意是想计算parr所指向的动态内存空间的大小,但是事与愿违,sizeof认为parr是个字符指针,因此返回的是该指针所占的空间(指针的存储用的是长整型,所以为4;第三个结果,由于*parr所代表的是parr所指的地址空间存放的字符,所以长度为1
************

四、参考资料:
SizeofStrlen的区别与联系(转) 

1.sizeof操作符的结果类型是size_t,它在头文件中typedefunsigned int类型。 
该类型保证能容纳实现所建立的最大对象的字节大小。 

2.sizeof是算符,strlen是函数。 

3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。 
sizeof还可以用函数做参数,比如: 
short f(); 
printf("%d\n", sizeof(f())); 
输出的结果是sizeof(short),即2 

4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。 

5.大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因 
char str[20]="0123456789"; 
int a=strlen(str); //a=10; 
int b=sizeof(str); //b=20; 

6.strlen的结果要在运行的时候才能计算出来,时用来计算字符串的长度,不是类型占内存的大小。 

7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。 

8.当适用了于一个结构类型时或变量, sizeof 返回实际的大小, 
当适用一静态地空间数组, sizeof 归还全部数组的尺寸。 
sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸 

9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址, 
如: 
fun(char [8]) 
fun(char []) 
都等价于 fun(char *) 
C++里参数传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小 
如果想在函数内知道数组的大小, 需要这样做: 
进入函数后用memcpy拷贝出来,长度由另一个形参传进去 
fun(unsiged char *p1, int len) 

unsigned char* buf = new unsigned char[len+1] 
memcpy(buf, p1, len); 


我们能常在用到 sizeof  strlen 的时候,通常是计算字符串数组的长度 
看了上面的详细解释,发现两者的使用还是有区别的,从这个例子可以看得很清楚: 

char str[20]="0123456789"; 
int a=strlen(str); //a=10; >>>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。 
int b=sizeof(str); //b=20; >>>> sizeof 计算的则是分配的数组 str[20] 所占的内存空间的大小,不受里面存储的内容改变。 

上面是对静态数组处理的结果,如果是对指针,结果就不一样了 

char* ss = "0123456789"; 
sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是 

长整型的,所以是
sizeof(*ss) 结果 1 ===》*ss是第一个字符 其实就是获得了字符串的第一位'0' 所占的内存空间,是char 

型的,占了 1  

strlen(ss)= 10 >>>> 如果要获得这个字符串的长度,则一定要使用 strlen

 

 

Day13

简单的指针外挂

效果图:

靶子:

//

//#include <stdio.h>

//#include <stdlib.h>

//#include <windows.h>

//int main(void)

//{

// int n = 9999;

//

// while (1)

// {

// printf("%d 地址:%X\n ", n, &n);

// Sleep(1000);

// n = n - 500;

// }

//

// return 0;

//}

 

指针外挂dll:

#include <stdio.h>

#include<Windows.h>

#include<stdlib.h>

_declspec(dllexport) int go()

//讲解 _declspec声明为外部调用  dllexport 声明dll导出。

 

{

 int *p = (int *)0x6cfd08; //将地址强制转换为指针类型。

 while (*p < 5000)

 {

  *p = 9999;

 }

 return 0;

}

 

 

 

注意事项:

 

被注入的程序和注入工具要在同一个账户下运行。

 

 

 

 

指针与地址的区别

指针是存放地址的变量,也是一个地址,大小是固定的。就是四个字节。指针也可以是一个常量,但需要const修饰符来修饰。

 

地址仅仅就是一个地址,地址是一个常量。

 

 

指针; 一个变量的地址

指针变量:专门存放变量地址的变量叫做指针变量。

 

指针有类型,修饰指针的类型 指定了指针所指向的地址的数据如何解析,从那里开始到那里结束。

而地址,只知道从那里开始,不知道从那里结束。

 

 

不同类型的指针自增的大小不一样

测试代码:

#include <stdio.h>

int main(int argc, char *argv[])

{

 char a;

 int b;

 double c;

 char *pa=&a;

 int *pb=&b;

 double *pc=&c;

 printf("a:%p\t b:%p\t c:%p\n",pa,pb,pc);

 ++pa;++pb;++pc;

 printf("a:%p\t b:%p\t c:%p\n",pa,pb,pc);

 ++pa;++pb;++pc;

 printf("a:%p\t b:%p\t c:%p\n",pa,pb,pc);

 ++pa;++pb;++pc;

 printf("a:%p\t b:%p\t c:%p\n",pa,pb,pc);

 printf("Hello, world\n");

 

 return 0;

}

 

 

 

 

 

指针的使用注意事项

指针使用时最好初始化。

 

不初始化能编译通过,但运行报错。

 

 

批量定义指针时

 

批量定义时需要每个指针前都有“*”

 

 

空指针

int * p ;

p=NULL;

 

 

 

 

 

 

 

 

加载静态链接库

#pragma comment(lib,"User32.lib")

 

列子:

#include <stdio.h>

#include <stdlib.h>

#include <windows.h>

#pragma comment(lib,"User32.lib")

int main(int argc, char *argv[])

{

 MessageBox(0,"a","b",0);

 int num;

 int *p=#

 *p=10;

 printf("Hello, world\n");

 system("calc");

 return 0;

}

 

 

 

 

制作静态库调用静态库

 静态块的制作:

1.编写函数,不需要主函数。

2.配置编译工具生成lib

VS :

在调试中对应项目的属性

 

 

 

 

gcc  加 -share   -o *lib

 

 

运行生成

 

 

编写头文件:

 

将函数的声明写入到对应的头文件中,头文件的名字可以和库文件的名字不一样。

VS中在头文件中增加头文件:

 

 

 

文件目录下的存放关系:

 

 

都在同一个文件夹目录下

 

需要调用时,需将lib和头文件存放在和需要调用的文件的同目录下。

 

调用时需要加载静态库:

#pragma comment (lib,"mylib")

 

 

动态库,动态执行,在需要的时候调用。

静态库在编译链接时链接编译进二进制文件中,值有c/C++调用。

 

 

 

 

 

 

动态库和静态库

静态库在windows平台下为  *.lib

在linux 下一般是  libxxx.a;

调用静态函数编译的文件比较大,在链接时将整个函数库的所有数据整合到目标代码中。

 

 

 

动态库在windows 平台下为*.dll文件

在linux平台下一般为libxxx.so文件。相对静态库,动态库在编译的时候并没有被编译进目标代码中,当程序需要调用该函数库内的相应的函数,才会调用该函数里的相应的函数,因此动态库所编译生成的文件一般相对较小。

 

 

在vs 下创建动态或者静态库

 

 

GCC 生成库参数

 

使用 ar 

 

 

 

 

间接访问和直接访问

 

 

 

 

直接访问:按变量地址存取变量值

 

间接访问: 通过存放变量地址的变量去访问变量。

 

 

 

 

空指针 零指针

定义指针的变量值为零

  

int *p =0;

 

#define  NULL 0

int * p =NULL

 

 

 

 

通过指针排列大小,不改变原始值

判断*p 所指向的值的大小

然后交换地址。

 

 

 

对窗口外科手术

测试对象 calc

 

 

 

有时候需要对子窗口操作,需要先获取父类窗口的句柄,并在句柄上再获取到子窗口的句柄。

示例代码:

HWND win = FindWindowA("#32770", "set应用");

 HWND button = FindWindowExA(win, NULL, "Button", "获取数据");

 if (button == NULL) //HWND指针,

 {

  printf("can not find ");

 }

 else

 {

  printf("find");

 }

 while (1)

 {

  Sleep(1000);

  ShowWindow(button, SW_HIDE); //隐藏某个窗口

  Sleep(1000);

  ShowWindow(button, SW_SHOW); //显示某个窗口

 }

 

//#define SW_HIDE 0

//#define SW_SHOWNORMAL 1

//#define SW_NORMAL 1

//#define SW_SHOWMINIMIZED 2

//#define SW_SHOWMAXIMIZED 3

//#define SW_MAXIMIZE 3

//#define SW_SHOWNOACTIVATE 4

//#define SW_SHOW 5

//#define SW_MINIMIZE 6

//#define SW_SHOWMINNOACTIVE 7

//#define SW_SHOWNA 8

//#define SW_RESTORE 9

//#define SW_SHOWDEFAULT 10

//#define SW_FORCEMINIMIZE 11

//#define SW_MAX 11

 

SW_HIDE 隐藏窗口并将活动状态传递给其它窗口。
 SW_MINIMIZE 最小化窗口并激活系统列表中的顶层窗口。
 SW_RESTORE 激活并显示窗口。如果窗口是最小化或最大化的,Windows恢复其原来的大小和位置。 
 SW_SHOW 激活窗口并以其当前的大小和位置显示。
 SW_SHOWMAXIMIZED 激活窗口并显示为最大化窗口。
 SW_SHOWMINIMIZED 激活窗口并显示为图标。 
 SW_SHOWMINNOACTIVE 将窗口显示为图标。当前活动的窗口将保持活动状态。
 SW_SHOWNA 按照当前状态显示窗口。当前活动的窗口将保持活动状态。
 SW_SHOWNOACTIVATE 按窗口最近的大小和位置显示。当前活动的窗口将保持活动状态。 

 

 

获取子窗口

HWND win = FindWindowA("#32770", "set应用");

 HWND button = FindWindowExA(win, NULL, "Button", "获取数据");

 

移动函数:

MoveWindow(win, 300, 300, 150, 180, 1);

 

 

 

 

 

Day14

函数指针调用函数 p与(*p

/** 以下摘自C++ primer plus.
这真是非常棒的语法为何pf()  (*pf)() 等价呢一种学派认为由于pf是函数指针*pf是函数因此应该将(*pf)()用作函数调用另一种学派认为函数名是指向该函数的指针指向函数的指针的行为应该与函数名相似因此应将pf()用作函数调用使用. C++进行了折衷----这两种方法都是正确的或者至少是允许的虽然他们在逻辑上是互相冲突的在认为种种折衷粗糙之前应该想到容忍逻辑上无法自圆其说的观点正是人类思维活动的特点.
**/

 

 

 

指针实战

指针使用前需要初始化:

 

指针变量作为函数参数---地址传递

  特点:共享内存,“双向”传递

 

指针陷阱:

交换地址不影响值

代码:

#include <stdio.h>

void swap(int *sp1,int *sp2)

{

 int n=0;

 int *p;

 p=&n;

 p=sp1;//交换数值失败,这里的sp1 和main函数里的mp1 不一样,不是同一个指针变量,交换sp1,sp2的地址对 mp1 mp2没有影响。

 sp1=sp2;

 sp2=p;

}

int main(int argc, char *argv[])

{

 int a,b;

 int *mp1,*mp2;

 scanf("%d%d",&a,&b);

 mp1=&a;

 mp2=&b;

 if (a<b)

 {

  swap(mp1,mp2);

 }

 printf("a=%d b=%d\n",*mp1,*mp2);

 

 return 0;

}

 

 

 

 

 

二级指针

指针的变量也是变量,占据一定的内存空间,有地址,因此可以用一个指针指向它,这称为指向指针的指针,或二级指针。

 

在32位系统中,指针的长度为4位

 

解析类型修饰符   *指针修饰符  指针变量名 = 地址;

 

二级指针 int **p =&n;

其中 int 用于解析 最终存放在地址所在的数据

而 * 指明这是一个指针,解析时按4位来解析。

 

多级指针:

 

int n;

 

int *p0=&n;

int **p1=&p0;

int ***p2=&p1;

int ****p3=&p2;

 

 

 

不同类型的指针之间的转换

不同类型的指针之间不可以转换。

 

 

在x64架构下指针大小为8位  64位程序

 

 

在x86架构下指针的大小为4位  32位程序

 

 

 

 

 

Day15

Getchar

getchar每次获取一个字符并输入

 

 

 

 

 

Day16

指针与数组

数组在内存中的存放是连续的。

 

 

 

一维数组的 数组名地址和&数组名地址一样

取*数组的地址异常

 

 

一维数组数组名的解析:

 

 

一维数组中 a 与&a 的区别:

 

 

 

 

 

对一维数组的测试:

a ,&a ,&a[0]都是存放的地址,*a 存放的是一维数组的第一个元素的值。 以下的所说的指针有歧义的指针,并没有定义指针。

 

a:

a的大小为一维数组所有元素的大小的总和,如果切换为double类型数组,那么都是80,在int和double类型下的数组计算 a和 a+1的大小都是4 ,a为一个指针。在int 类型下的数组的一个元素的大小为4字节,而double类型下的一个元素的大小为8字节,在int 类型数组下,a+1比a 的地址增加了4字节,而在double类型下 a+1比a的地址增加了8字节,那么a是一个指向元素的指针。

 

&a:

&a在int和double类型的数组下大小都是4,&a是一个指针,在int 类型的数组下,&a+1 比&a的地址移动了40字节,而在double类型下,&a+1比&a的地址移动了80字节,40字节为一个int数组的大小,80为一个double类型的数组的大小,&a是指向数组的指针,一次移动一个数组的长度。

 

 

&a[0]:

&a[0]和a,&a的地址是一样的,在int 数组下移动一次为4字节,在double数组下移动一次为8字节,移动一次为数组中一个元素的大小。&a[0]为一维数组a第一个元素的地址。 &a[0]+1 移动一次,增加了与一个元素的大小。&a[0]+1 &a[1] 效果一样。

 

*a:

*a取出a中存放的内容,*a 取出为一维数组的第一个元素。*a+1 为2 如果加20 则为21,a 中存放的是一维数组的元素。 那么是否可以这样理解呢?*&a,&a为一维数组的地址,*取内容,*&a取一维数组地址对应的内容,而取出的是一维数组元素的地址,一维数组存放着数组元素。

 

 

 

int 型一维数组:

 

 

 

dobule 型一维数组:

 

 

 

 

 

关于&(a+1)

a+1 是一维数组的第二个元素的地址,在内存中没有实体,无法取到地址。

 

临时变量在寄存器中运算,在内存中没有地址,无法取的地址。

 

&取地址符的操作对象需要是一个左值。

 

数组名a 本质是一个常量指针,[]不仅可以作用于数组名也可以作用于指针。

 

a[i] 等价于 *(a+i) 

&a[i]等价于 a+i

 

a为一维数组名,int *p=a ,p是一个变量指针

数组的本质是一个常量指针指向的一片内存区域

 

[] 本质  a[3]  在内存中解析为*(a+3 )

 

 

 

 

 

 

 

 

 

 

 

指针引用多维数组

int a[3][4]={  {1,3,5,7},{9,11,13,15},{17,19,21,23}  };

 

二维数组可以看成一个一维数组,只是每个元素又是一个数组。

一维数组 b[3]  b[0]={1,3,5,7}  b[1]={9,11,13,15} b[2]={17,19,21,23 }

 

 

 

 

 

 

 

 

 

 

验证代码:

 

行指针:

int (*pb)[5]=b

 

 

 

 

 

 

 

 

 

对于二维数组

*b  &b b 的区别

 

 

 

对于*b &b b地址是一样的。

 

 

*b 移动一位为4字节  b移动一位为20字节 也就是五个Int型的元素, &b移动一位为80字节 为一个数组的大小。

*b 指向元素  b指向行 &b为数组的指针

 

 

 

*b 是指向单个元素的指针,*b的大小一共为20,二维数组共20个元素。

b 是指向数组中下一层的元素的指针,下一层的元素是有多少行,那么b是指向 行元素的指针。

&b 是指向数组的指针,大小为80 一次移动80字节。

 

那么是否这样理解?b 一般就是存放是指向数组第一层的元素的指针,而*b如果是指针的话则是指向第一层指针所存放的元素的指针。以此类推。&b是指向数组整个数组的指针,将数组整个作为一个元素。

 

 

 

 

数组函数指针

 

 

 

数组名是一个常量指针,不是参数,用sizeof 就是整体数组大小。

数组名用做参数,是一个 指针,数组是函数副本机制的例外。

数组拷贝数据很麻烦很费空间,所以是例外。

 

 

 

 

指针与const

const int *p

 

这个指针不能改变指向的数据,可以改变指向谁。

和int const *p 是等价的。

 

int * const p

 

不可以改变指针的指向

 

 

 

const int * const p=&x

p指针不能改变指向也不能改变数据

 

 

 

 

 

字符串

字符串以“\0”结尾

 

 

 

C语言的内存的分配机制

指向字符串常量的指针和字符串数组在使用时是有区别的。
看下面两个函数

//执行通过,返回“hello world”
char *PtrChar()
{
    char *pc = "hello world";
    return *r;
}
//执行错误,不能返回局部变量
char *ArrChar()
{
    char ac[] = "hello world";
    return ac;
}
出现上面的原因在于指针pc指向的字符串“hello world”存放在文字常量区,而数组ac中存放的字符串存放在栈区,当函数结束时,栈中的数据自动释放。

再看下面:
char* r = "hello word!";// 
char b[]="hello word!";


//错误,r指向的是文字常量区,在编译时就确定的,在程序结束时自动释放。试图修改文字常量会引起错误
*r = 'w';

//正确,修改数组中的第一个字符,b是在栈上分配的
*b = 'w';

一个由C/C++编译的程序占用的内存分为以下几个部分:

1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4、文字常量区 —常量字符串就是放在这里的,程序结束后由系统释放。
5、程序代码区—存放函数体的二进制代码。

二、例子程序
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
    int b; //栈
    char s[] = "abc"; //栈
    char *p2; //栈

    //"123456/0"在文字常量区,而指针p3在栈上,p3指向文字常量区上一片内存
    char *p3 = "123456"; 
    static int c =0; 全局(静态)初始化区

    //分配得来得10和20字节的区域就在堆区。
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20);

    //123456/0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。
    strcpy(p1,"123456"); 
}

二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数,如p1 = (char *)malloc(10);在C++中用new运算符,如p2 = new char[10],但是注意p1、p2本身是在栈中的。

2.2
申请后系统的响应

栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在 WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。

堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活

2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。

堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

2.6存取效率的比较char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include <stdio.h>;
void main()
{
    char a = 1;
    char c[] = "1234567890";
    char *p ="1234567890";
    a = c[1];
    a = p[1];
    return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。

2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

堆和栈的区别主要分:
操作系统方面的堆和栈,如上面说的那些,不多说了。
还有就是数据结构方面的堆和栈,这些都是不同的概念。这里的堆实际上指的就是(满足堆性质的)优先队列的一种数据结构,第1个元素有最高的优先权;栈实际上就是满足先进后出的性质的数学或数据结构。虽然堆栈,堆栈的说法是连起来叫,但是他们还是有很大区别的,连着叫只是由于历史的原因。

 

 

 

内存分配

1、内存分配方式

  内存分配方式有三种:

  (1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。

  (2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

  (3)从堆上分配,亦称动态内存分配。程序在运行的时候用mallocnew申请任意多少的内存,程序员自己负责在何时用freedelete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

2、常见的内存错误及其对策

  发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。常见的内存错误及其对策如下:

  内存分配未成功,却使用了它。

  编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行

  检查。如果是用mallocnew来申请内存,应该用if(p==NULL) if(p!=NULL)进行防错处理。

  内存分配虽然成功,但是尚未初始化就引用它。

  犯这种错误主要有两个起因:一是没有初始化的观念;二是误以为内存的缺省初值全为零,导致引用初值错误(例如数组)。内存的缺省初值究竟是什么并没有统一的标准,尽管有些时候为零值,我们宁可信其无不可信其有。所以无论用何种方式创建数组,都别忘了赋初值,即便是赋零值也不可省略,不要嫌麻烦。

  内存分配成功并且已经初始化,但操作越过了内存的边界。

  例如在使用数组时经常发生下标1”或者1”的操作。特别是在for循环语句中,循环次数很容易搞错,导致数组操作越界。

  忘记了释放内存,造成内存泄露。

  含有这种错误的函数每被调用一次就丢失一块内存。刚开始时系统的内存充足,你看不到错误。终有一次程序突然死掉,系统出现提示:内存耗尽。

  动态内存的申请与释放必须配对,程序中mallocfree的使用次数一定要相同,否则肯定有错误(new/delete同理)。

  释放了内存却继续使用它。

  有三种情况:

  (1)程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。

  (2)函数的return语句写错了,注意不要返回指向栈内存指针或者引用,因为该内存在函数体结束时被自动销毁。

  (3)使用freedelete释放了内存后,没有将指针设置为NULL。导致产生野指针

  【规则1】用mallocnew申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。

  【规则2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。

  【规则3】避免数组或指针的下标越界,特别要当心发生1”或者1”操作。

  【规则4】动态内存的申请与释放必须配对,防止内存泄漏。

  【规则5】用freedelete释放了内存之后,立即将指针设置为NULL,防止产生野指针

3、指针与数组的对比

  C /C程序中,指针和数组在不少地方可以相互替换着用,让人产生一种错觉,以为两者是等价的。

  数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。数组名对应着(而不是指向)一块内存,其地址与容量在生命期内保持不变,只有数组的内容可以改变。

  指针可以随时指向任意类型的内存块,它的特征是可变,所以我们常用指针来操作动态内存。指针远比数组灵活,但也更危险。

  下面以字符串为例比较指针与数组的特性。

  3.1 修改内容

  示例3-1中,字符数组a的容量是6个字符,其内容为helloa的内容可以改变,如a[0]= ‘X’。指针p指向常量字符串“world”(位于静态存储区,内容为world),常量字符串的内容是不可以被修改的。从语法上看,编译器并不觉得语句 p[0]= ‘X’有什么不妥,但是该语句企图修改常量字符串的内容而导致运行错误。

1. char a[] = “hello”;

2. a[0] = ‘X’;

3. cout << a << endl;

4. char *p = “world”; // 注意p指向常量字符串

5. p[0] = ‘X’; // 编译器不能发现该错误

6. cout << p << endl;

复制代码

示例3.1 修改数组和指针的内容

  3.2 内容复制与比较

  不能对数组名进行直接复制与比较。示例7-3-2中,若想把数组a的内容复制给数组b,不能用语句 b = a ,否则将产生编译错误。应该用标准库函数strcpy进行复制。同理,比较b和a的内容是否相同,不能用if(b==a) 来判断,应该用标准库函数strcmp进行比较。

  语句p = a 并不能把a的内容复制指针p,而是把a的地址赋给了p。要想复制a的内容,可以先用库函数malloc为p申请一块容量为strlen(a)  1个字符的内存,再用strcpy进行字符串复制。同理,语句if(p==a) 比较的不是内容而是地址,应该用库函数strcmp来比较。

1. // 数组

2. char a[] = "hello";

3. char b[10];

4. strcpy(b, a); // 不能用 b = a;

5. if(strcmp(b, a) == 0) // 不能用 if (b == a)

6. 

7. // 指针

8. int len = strlen(a);

9. char *p = (char *)malloc(sizeof(char)*(len 1));

10. strcpy(p,a); // 不要用 p = a;

11. if(strcmp(p, a) == 0) // 不要用 if (p == a)

12. 

复制代码

示例3.2 数组和指针的内容复制与比较

  3.3 计算内存容量

  用运算符sizeof可以计算出数组的容量(字节数)。示例7-3-3(a)中,sizeof(a)的值是12(注意别忘了’’)。指针p指向a,但是 sizeof(p)的值却是4。这是因为sizeof(p)得到的是一个指针变量的字节数,相当于sizeof(char*),而不是p所指的内存容量。 C /C语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

  注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。示例7-3-3(b)中,不论数组a的容量是多少,sizeof(a)始终等于sizeof(char *)。

1. char a[] = "hello world";

2. char *p = a;

3. cout<< sizeof(a) << endl; // 12字节

4. cout<< sizeof(p) << endl; // 4字节

复制代码

示例3.3(a) 计算数组和指针的内存容量

1. void Func(char a[100])

2. {

3.  cout<< sizeof(a) << endl; // 4字节而不是100字节

4. }

复制代码

示例3.3(b) 数组退化为指针


4、指针参数是如何传递内存的?

  如果函数的参数是一个指针,不要指望用该指针去申请动态内存。示例7-4-1中,Test函数的语句GetMemory(str, 200)并没有使str获得期望的内存,str依旧是NULL,为什么?

1. void GetMemory(char *p, int num)

2. {

3.  p = (char *)malloc(sizeof(char) * num);

4. }

5. void Test(void)

6. {

7.  char *str = NULL;

8.  GetMemory(str, 100); // str 仍然为 NULL

9.  strcpy(str, "hello"); // 运行错误

10. }

复制代码

示例4.1 试图用指针参数申请动态内存

  毛病出在函数GetMemory中。编译器总是要为函数的每个参数制作临时副本,指针参数p的副本是 _p,编译器使 _p = p。如果函数体内的程序修改了_p的内容,就导致参数p的内容作相应的修改。这就是指针可以用作输出参数的原因。在本例中,_p申请了新的内存,只是把 _p所指的内存地址改变了,但是p丝毫未变。所以函数GetMemory并不能输出任何东西。事实上,每执行一次GetMemory就会泄露一块内存,因为没有用free释放内存。

  如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,见示例4.2。

1. void GetMemory2(char **p, int num)

2. {

3.  *p = (char *)malloc(sizeof(char) * num);

4. }

5. void Test2(void)

6. {

7.  char *str = NULL;

8.  GetMemory2(&str, 100); // 注意参数是 &str,而不是str

9.  strcpy(str, "hello");

10.  cout<< str << endl;

11.  free(str);

12. }

复制代码

示例4.2用指向指针的指针申请动态内存

  由于“指向指针的指针”这个概念不容易理解,我们可以用函数返回值来传递动态内存。这种方法更加简单,见示例4.3。

1. char *GetMemory3(int num)

2. {

3.  char *p = (char *)malloc(sizeof(char) * num);

4.  return p;

5. }

6. void Test3(void)

7. {

8.  char *str = NULL;

9.  str = GetMemory3(100);

10.  strcpy(str, "hello");

11.  cout<< str << endl;

12.  free(str);

13. }

复制代码

示例4.3 用函数返回值来传递动态内存

  用函数返回值来传递动态内存这种方法虽然好用,但是常常有人把return语句用错了。这里强调不要用return语句返回指向“栈内存”的指针,因为该内存在函数结束时自动消亡,见示例4.4。

1. char *GetString(void)

2. {

3.  char p[] = "hello world";

4.  return p; // 编译器将提出警告

5. }

6. void Test4(void)

7. {

8.  char *str = NULL;

9.  str = GetString(); // str 的内容是垃圾

10.  cout<< str << endl;

11. }

复制代码

示例4.4 return语句返回指向“栈内存”的指针

  用调试器逐步跟踪Test4,发现执行str = GetString语句后str不再是NULL指针,但是str的内容不是“hello world”而是垃圾。
如果把示例4.4改写成示例4.5,会怎么样?

1. char *GetString2(void)

2. {

3.  char *p = "hello world";

4.  return p;

5. }

6. void Test5(void)

7. {

8.  char *str = NULL;

9.  str = GetString2();

10.  cout<< str << endl;

11. }

复制代码

示例4.5 return语句返回常量字符串

  函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。

5、杜绝“野指针”

  “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种:

  (1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如

1. char *p = NULL;

2. char *str = (char *) malloc(100);

复制代码

(2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。

  (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:

1. class A

2. {

3.  public:

4.   void Func(void){ cout << “Func of class A” << endl; }

5. };

6. void Test(void)

7. {

8.  A *p;

9.  {

10.   A a;

11.   p = &a; // 注意 的生命期

12.  }

13.  p->Func(); // p野指针

14. }

复制代码

函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。但奇怪的是我运行这个程序时居然没有出错,这可能与编译器有关。


6、有了malloc/free为什么还要new/delete?

  malloc与free是C /C语言的标准库函数,new/delete是C 的运算符。它们都可用于申请动态内存和释放内存。

  对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

   因此C 语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。我们先看一看malloc/free和new/delete如何实现对象的动态内存管理,见示例6。

1. class Obj

2. {

3.  public :

4.   Obj(void){ cout << “Initialization” << endl; }

5.   ~Obj(void){ cout << “Destroy” << endl; }

6.   void Initialize(void){ cout << “Initialization” << endl; }

7.   void Destroy(void){ cout << “Destroy” << endl; }

8. };

9. void UseMallocFree(void)

10. {

11.  Obj *a = (obj *)malloc(sizeof(obj)); // 申请动态内存

12.  a->Initialize(); // 初始化

13.  //…

14.  a->Destroy(); // 清除工作

15.  free(a); // 释放内存

16. }

17. void UseNewDelete(void)

18. {

19.  Obj *a = new Obj; // 申请动态内存并且初始化

20.  //…

21.  delete a; // 清除并且释放内存

22. }

复制代码

示例6 用malloc/free和new/delete如何实现对象的动态内存管理

  类Obj的函数Initialize模拟了构造函数的功能,函数Destroy模拟了析构函数的功能。函数UseMallocFree中,由于 malloc/free不能执行构造函数与析构函数,必须调用成员函数Initialize和Destroy来完成初始化与清除工作。函数 UseNewDelete则简单得多。

  所以我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

  既然new/delete的功能完全覆盖了malloc/free,为什么C 不把malloc/free淘汰出局呢?这是因为C 程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

  如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存 ”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

7、内存耗尽怎么办?

  如果在申请动态内存时找不到足够大的内存块,malloc和new将返回NULL指针,宣告内存申请失败。通常有三种方式处理“内存耗尽”问题。

  (1)判断指针是否为NULL,如果是则马上用return语句终止本函数。例如:

1. void Func(void)

2. {

3.  A *a = new A;

4.  if(a == NULL)

5.  {

6.   return;

7.  }

8.  

9. }

复制代码

(2)判断指针是否为NULL,如果是则马上用exit(1)终止整个程序的运行。例如:

1. void Func(void)

2. {

3.  A *a = new A;

4.  if(a == NULL)

5.  {

6.   cout << “Memory Exhausted” << endl;

7.   exit(1);

8.  }

9.  

10. }

复制代码

(3)为new和malloc设置异常处理函数。例如Visual C 可以用_set_new_hander函数为new设置用户自己定义的异常处理函数,也可以让malloc享用与new相同的异常处理函数。详细内容请参考C 使用手册。

  上述(1)(2)方式使用最普遍。如果一个函数内有多处需要申请动态内存,那么方式(1)就显得力不从心(释放内存很麻烦),应该用方式(2)来处理。

  很多人不忍心用exit(1),问:“不编写出错处理程序,让操作系统自己解决行不行?”

  不行。如果发生“内存耗尽”这样的事情,一般说来应用程序已经无药可救。如果不用exit(1) 把坏程序杀死,它可能会害死操作系统。道理如同:如果不把歹徒击毙,歹徒在老死之前会犯下更多的罪。

  有一个很重要的现象要告诉大家。对于32位以上的应用程序而言,无论怎样使用malloc与new,几乎不可能导致“内存耗尽”。我在Windows 98下用Visual C 编写了测试程序,见示例7。这个程序会无休止地运行下去,根本不会终止。因为32位操作系统支持“虚存”,内存用完了,自动用硬盘空间顶替。我只听到硬盘嘎吱嘎吱地响,Window 98已经累得对键盘、鼠标毫无反应。

  我可以得出这么一个结论:对于32位以上的应用程序,“内存耗尽”错误处理程序毫无用处。这下可把Unix和Windows程序员们乐坏了:反正错误处理程序不起作用,我就不写了,省了很多麻烦。

  我不想误导读者,必须强调:不加错误处理将导致程序的质量很差,千万不可因小失大。

1. void main(void)

2. {

3.  float *p = NULL;

4.  while(TRUE)

5.  {

6.   p = new float[1000000];

7.   cout << “eat memory” << endl;

8.   if(p==NULL)

9.    exit(1);

10.  }

11. }

复制代码

示例7试图耗尽操作系统的内存


8、malloc/free 的使用要点

  函数malloc的原型如下:

1. void * malloc(size_t size);

复制代码

用malloc申请一块长度为length的整数类型的内存,程序如下:

1. int *p = (int *) malloc(sizeof(int) * length);

复制代码

我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。

  * malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。

  * malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。我们通常记不住int, float等数据类型的变量的确切字节数。例如int变量在16位系统下是2个字节,在32位下是4个字节;而float变量在16位系统下是4个字节,在32位下也是4个字节。最好用以下程序作一次测试:

1. cout << sizeof(char) << endl;

2. cout << sizeof(int) << endl;

3. cout << sizeof(unsigned int) << endl;

4. cout << sizeof(long) << endl;

5. cout << sizeof(unsigned long) << endl;

6. cout << sizeof(float) << endl;

7. cout << sizeof(double) << endl;

8. cout << sizeof(void *) << endl;

复制代码

在malloc的“()”中使用sizeof运算符是良好的风格,但要当心有时我们会昏了头,写出 p = malloc(sizeof(p))这样的程序来。

  * 函数free的原型如下:

1. void free( void * memblock );

复制代码

为什么free 函数不象malloc函数那样复杂呢?这是因为指针p的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p是 NULL指针,那么free对p无论操作多少次都不会出问题。如果p不是NULL指针,那么free对p连续操作两次就会导致程序运行错误。

9、new/delete 的使用要点

  运算符new使用起来要比函数malloc简单得多,例如:

1. int *p1 = (int *)malloc(sizeof(int) * length);

2. int *p2 = new int[length];

复制代码

这是因为new内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new的语句也可以有多种形式。例如

1. class Obj

2. {

3.  public :

4.   Obj(void); // 无参数的构造函数

5.   Obj(int x); // 带一个参数的构造函数

6.   

7. }

8. void Test(void)

9. {

10.  Obj *a = new Obj;

11.  Obj *b = new Obj(1); // 初值为1

12.  

13.  delete a;

14.  delete b;

15. }

复制代码

如果用new创建对象数组,那么只能使用对象的无参数构造函数。例如

1. Obj *objects = new Obj[100]; // 创建100个动态对象

复制代码

不能写成

1. Obj *objects = new Obj[100](1);// 创建100个动态对象的同时赋初值1

复制代码

在用delete释放对象数组时,留意不要丢了符号‘[]’。例如

1. delete []objects; // 正确的用法

2. delete objects; // 错误的用法

复制代码

后者相当于delete objects[0],漏掉了另外99个对象。

10、一些心得体会

  我认识不少技术不错的C /C程序员,很少有人能拍拍胸脯说通晓指针与内存管理(包括我自己)。我最初学习C语言时特别怕指针,导致我开发第一个应用软件(约1万行C代码)时没有使用一个指针,全用数组来顶替指针,实在蠢笨得过分。躲避指针不是办法,后来我改写了这个软件,代码量缩小到原先的一半。

  我的经验教训是:

  (1)越是怕指针,就越要使用指针。不会正确使用指针,肯定算不上是合格的程序员。

  (2)必须养成“使用调试器逐步跟踪程序”的习惯,只有这样才能发现问题的本质。 

 

 

 

修改const


 

对与有const修饰的变量可以通过间接的方式修改。

 

 

对于指针可以强制通过强制转换指针的方式来修改指针所指向的内容。

 

通过其他指针也可以修改。

 

 

 

Day17

Day18

Day19


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值