程序设计基础
预处理器指令
#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}};
,可以用下面的表格来描述
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);
共性与差异的分析
代码中使用函数的目的
一是可以复用代码;
二是组织代码,使代码具有层次性及逻辑性
一个很好的经验
一段代码重复出现了三次,就应该将其封装成一个函数。