程序设计基础

预处理器指令

#define
#include
#if,#ifdef,#ifndef,#elif,#else,#endif

常见用法

将带参宏的参数转为字符串常量

#define PRINT_INT(i) printf(#i"=%d\n",i)

指针与内存地址的关系

他们是等价的,只是在不同语境中的称呼
在讨论内存时,会用内存地址
在讨论一个保存内存地址的变量时,称为指针
当变量A的值是另一个变量B的内存地址时,会说A指向了变量B

const

const修饰的是紧跟在它后面的单词

char * const src;	//src是只读的,不可改变的
const char *src;	//src指向的内容是只读的,不可改变的
const char * const src;	//src与所指对象都是只读的,不可改变的

将某个数存入内存的某个地址

如要将数据0x05存入内存地址0x22FF74中,用C语言如何实现呢?
1.先告诉编译器,这个数是一个内存地址(指针):(unsigned int *)0x22FF74
2.将这个指针赋值:*(unsigned int *) 0x22FF74 = 0x05

数组与其指针

定义

int data[3];	//data是int数组(元素个数为3)

其指针

就是数组名data。指向数组的首个元素。

获取数组的整个长度

sizeof(data)

获取数组元素个数

sizeof(data)/sizeof(data[0])
or
#define NELEMS(data) (sizeof(data)/sizeof(data[0]))

如何访问

使用下标法

数组的数组与其指针

数组的数组--二维数组

二维数组int data[2][3] = {{1,2,3},{4,5,6}};,可以用下面的表格来描述
image
data是由data[0],data[1]这两个元素构成的一维数组。
data[0]是由data[0][0],data[0][1],data[0][2]三个元素构成的一维数组。
data[1]是由data[1][0],data[1][1],data[1][2]三个元素构成的一维数组。


请注意,以下是精彩段落

由于表达式中的数组名可以被解释为指针.
data是指向data[0]的指针

data == &data[0]
*data == data[0]

data[0]是指向data[0][0]的指针

data[0] == &data[0][0]
*data[0] == data[0][0]

故有

data == &data[0] == &(&data[0][0])
*data == data[0] == &data[0][0]
**data == data[0][0]

练习
若有定义int a[2][3] = {{1},{2,3}},则 a[1][0]的值是多少
分析:
二维数组是基于一维数组变化而来了,我们可以将题目中的数据拆成一维数组来分析

int a0[3] = {1};
int a1[3] = {2,3};

故由此可知,题目答案为 2

二维数组的指针

按照变量的声明规则,将data取出后,余下的就是data数据类型。这是开发编译器时约定的。
对一维数组int test[2]int [2]就是test的数据类型,test是指向int的指针,即int *
对于二维数组int a[2][3] ,int [2][3]就是a的数据类型。
a[0] a[1]的数据类型是int [3]
a是指向a[0]的指针,而a[0]的数据类型是int [3],即a是指向int [3]的指针,即 int (*)[3]

将二维数组作为函数参数

方法一

int sum (int data[][2], int size);

方法二

int sum(int (*pdata)[2], int size);

用法

int data[3][2] = {{1,2},{3,4},{5,6}};
int total = sum(data, 3);

对二维数组的常用操作

获取二维数组的相关数据

sizeof(data)整个二维数组的空间大小
sizeof(data[0])一行元素的空间大小
sizeof(data[0][0])单个元素的空间大小
sizeof(data)/sizeof(data[0]) 行数
sizeof(data[0])/sizeof(data[0][0])列数
sizeof(data)/sizeof(data[0][0])元素个数

找到二维数组中的最大元素

可以将二维数组转为一维数组来进行搜索

int max(int *pdata, int size);

用法

int data[3][2] = {{1,2},{3,4},{5,6}};
int n = sizeof(data)/sizeof(data[0][0]);
int max = max((int *)data, n);

清空二维数组

memset(data, 0, sizeof(data));

对二维数组的某一行进行清0

memset(data[row], 0, sizeof(data[0]));

对二维数组的某一列进行清0

由于数组是按行而不按列存储,因此操作二维数组中的列元素就相对复杂一些。
下面是对数组data的第i列清0

int data[row][col],(*pData)[col],i;
for(pData = &data[0];pData < &data[row];pData++)
	(*pData)[i] = 0;	//对某行的i列的元素清零

or

int data[row][col],i
for(int r = 0; r < row; r ++)
	data[r][i] = 0;	//对r行i列的元素清零

指针数组

普通数组里存得是相同类型的字符或数字,而指针数组存储的是相同类型的指针,比如:

int data0,data1,data2;
int *ptr[3] = {&data0,&date1,&date2};

其中ptr是由指向由int类型的指针组成的数组。

ptr[0] == &data0 -> data0
*ptr[0] == data0

常用方法

组织多个子符串

char *key_word[5] = {"eagle","cat","and","dog","ball"};

key_word[0]的类型是char *
key_word的类型是char **
虽然这些字符看起来像是存储在key_word指针数组变量中,但是实际上只存了它们的指针而已,每一个指针指向其对应字符串的第一个字符。

组织多个函数

函数指针就是函数名,也可以显式声明

int (*pf1)(int,int);
int (*pf2)(int,int);

typedef int (*p_int_f_t)(int,int);
p_int_f_t p_fun[2] = {pf1,pf2};

字符串与指针

动态分配内存

程序在运行时请求内存,被分配的空间称之为HEAP,虽然计算机在硬件上不直接支持HEAP,但是C函数库(stdlib.h)提供了申请与释放的函数,在运行时根据需要申请内存空间,在不需要时释放。

malloc()

void *malloc(unsigned int size);
	void *表示函数返回值是一个指针,从C99开始,void *类型指针可以赋值给摺有类型的指针变量
	size 为所需内存的字节数

eg.
int *pi = malloc(sizeof(int));
if (NULL != pi)
{
	//成功申请到内存
}
else
{
	//没有申请到内存
}

calloc()

比malloc()函数更好用函数

void *calloc (size_t nmeb, size_t size);

为nmeb个元素的数组分配内存,每个元素的大小为size 字节。在内存分配成功后,会将这片内存全部清0。

如,申请n个整数的数组,并初始化为0
pi = calloc(n,sizeof(int));

技巧:将calloc函数的第一个参数输入“1”,就可以为任何类型的数据项分配空间。
申请一块类型空间,同时清为0

struct point {int x,y;} *pi;
pi = calloc(1, sizeof(struct point));

realloc()

在原有分配的内存基础上,扩充内存。

void *realloc(void *pointer, unsigned int size);

alloc是allocate分配的缩写,前缀re是重新分配的意思。
如果原内存后面还有足够空闲内存的话,realloc()只是修改分配表,还是返回原内存地址;
如果没有足够空闲内存,realloc()会申请新的内存,然后将原内存的数据复制到新内存中,原内存将被free()掉,realloc()返回新的内存地址。

比如,有5个int型元素的数组变量需要分配内存

int *pi = malloc(5*sizeof(int));

但是在使用时发现,申请的空间不够了,需要10个才够用,那么需要先释放内存,然后再申请新的内存。

free(pi);
pi = malloc(10*sizeof(int));

为了保留原来的数据,需要再做一些工作

int *temp = pi;	//让temp 指向原内存
pi = malloc(10*sizeof(int));	让pi指向新内存
memcpy(pi,temp,5*sizeof(int));	将原内存的数据复制到新内存
free(temp);	//释放原内存

但上面的工作也可以由下面的一句话来完成

pi = realloc(pi,10*sizeof(int))

free()

通常内存申请与释放配对使用,当申请的内存使用完毕后,如果不及时释放掉,就会导致可用内存空间减少,也就是内存泄露,进而影响程序的正常运行。

void free(void *pointer);

悬空指针的出现

char *pi = malloc(5);
free(pi);	//释放掉pi所指向的内存块,但是pi依旧指向这块空间,此时的pi被称为悬空指针
strcpy(pi,"abc");	//错误

更好的方法

由于free()函数不会检查传入的参数是否为NULL,也不会返回前将指针设置为NULL,因此为了安全起见,要创建自己的free函数。

void safer_free(void **pp)
{
	if (pp != NULL && *pp != NULL)
	{
		free(*pp);
		*pp = NULL;
	}
}

为了简化使用,可以将其定义成宏,可以省略类型转换和传递指针的地址
#define SAFER_FREE(p) safer_free((void **)&p)
其调用形式如下:

int pi = malloc(sizeof(int));
SAFER_FREE(pi);

共性与差异的分析

代码中使用函数的目的
一是可以复用代码;
二是组织代码,使代码具有层次性及逻辑性

一个很好的经验
一段代码重复出现了三次,就应该将其封装成一个函数。

posted @ 2021-06-11 18:49  海林的菜园子  阅读(118)  评论(0编辑  收藏  举报