抽象数据类型(ADT)是C程序员不可或缺的工具,这类ADT有链表、堆栈、队列和树等,前面我们已经讨论过了链表,接下来我们来学习堆栈、队列和树。
所有的ADT都需要确定一件事情–如何获取内存来存储值,这里有三种方案:
1)静态数组
2)动态内存分配
3)动态链表存储
本章主要讲解堆栈的操作方式,并用上述三种方式分别来实现堆栈的操作。
栈的特点:后进先出(Last in First out –LIFO)
例如向仓库里堆放货品,后放进去的靠近门口,因此也会先拿出去
Leave no regrets every day
抽象数据类型(ADT)是C程序员不可或缺的工具,这类ADT有链表、堆栈、队列和树等,前面我们已经讨论过了链表,接下来我们来学习堆栈、队列和树。
所有的ADT都需要确定一件事情–如何获取内存来存储值,这里有三种方案:
1)静态数组
2)动态内存分配
3)动态链表存储
本章主要讲解堆栈的操作方式,并用上述三种方式分别来实现堆栈的操作。
栈的特点:后进先出(Last in First out –LIFO)
例如向仓库里堆放货品,后放进去的靠近门口,因此也会先拿出去
本章主要来讲一些之前没用用过的标准函数库。分为整数函数、浮点型函数、日期和时间函数等。
整数函数的返回值为整型值,分为三类:算术、随机数和字符串转换。
标准函数库包含了4个整型算术函数—abs\labs\ div\ldiv
原型:int abs(int value);
函数说明:abs用来计算传入参数的绝对值,然后将结果返回。
用法:
int answer;
answer = abs(-12);//answer 返回为-12的绝对值,结果为12
本章主要讨论ANSI C的输入和输出(I/O)函数。首先需要看两个错误和退出的函数。
原型:void perror(char const *message);
头文件: < stdio.h> , < stdlib.h>
如果message不是NULL,并且指向一个非空的字符串,perror函数就打印出这个字符串。1
2
3
4
5
6 FILE *fp;
fp = fopen(“/home/tianger/test”,”r”);
if(NULL == fp)
{
perror(“/home/tianger/test”);
}
输出:
/home/tianger/test:No such file or directory
预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏(宏macro是一段重复文字的简短描写)替代。预处理器可由语言(如C,PHP要求或以后作为提供额外功能(诸如为FORTRAN提供Ratfor预处理器)的附加软件。
ANSI C定义了一些宏。虽然每一个可以供您使用的编程,预定义宏不应该被直接修改。
符号 | 例子 | 含义 |
---|---|---|
DATE | “Jun 18 2016” | 文件被编译的时间 |
TIME | “15:47:03” | 文件被编译的时间 |
FILE | “test.c” | 进行编译的源文件名 |
LINE | “8” | 文件当前的行号 |
STDC | “1” | 如果编译器遵循ANSI C,其值就为1,否则未定义 |
本文重点讲的函数指针为 回调函数和转移表。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方法直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
⑴定义一个回调函数;
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
1.我们在路上遇到一个好多年没见到的朋友,要了朋友的手机好,跟他说我回去打给你
1)遇见朋友–主函数
2)我打电话给朋友,通过朋友的手机号,找到朋友–(回调)
2.寄快递的情形
打电话给快递员,告诉他我的地址(理解成回调函数的地址),快递员在有空的时候根据这个地址来找到你取件。
1)打电话给快递员,告诉他我的地址–主函数(我的地址–回调函数地址)
2)快递员处理完其他事情后,通过这个地址(回调)来找到你取件,执行完回调里面的内容。
示例:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/*回调函数*/
void *call_phone_number(int number )
{
printf("phone number = %d\n",number); /*通过号码找到朋友*/
}
/*调用函数*/
void start_call( void *call_back(int number))
{
int phone_number = 12345;
call_back(phone_number);
}
int main(void)
{
start_call(call_phone_number);
return 0;
}
用函数指针的形式来替换switch、if/else的方式
一个计算器实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22double add(double,double);
double sub(double,double);
double mul(double,double);
double div(double,double);
switch(oper)
{
case ADD:
result = add(op1mop2);
break;
case SUB:
result = sub(op1mop2);
break;
case MUL:
result = mul(op1mop2);
break;
case DIV:
result = div(op1mop2);
break;
default:
break;
}
上述代码改成转移表只需要两个步骤。
首先,声明并初始化一个函数指针数组。1
2
3
4double (*oper_func[])(double,double)={add,sub,mil,div};
调用:
result = oper_func[oper](op1,op2);
oper从数组中选择正确的函数指针,而函数调用操作符将执行这个函数
链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的。链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针域。
使用链表结构可以克服数组链表需要预先知道数据大小的缺点,链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。链表最明显的好处就是,常规数组排列关联项目的方式可能不同于这些数据项目在记忆体或磁盘上顺序,数据的存取往往要在不同的排列顺序中转换。链表允许插入和移除表上任意位置上的节点,但是不允许随机存取。
1)是由结构体和指针构成的。
2)包括两个部分一个是数据域和指针域。
3)链表中的结点分为两类:头结点和一般结点。头结点是没有数据域的。
4)基本操作有:初始化链表,增加结点和删除结点,求链表的长度等等。
链表主要包括:单向链表,双向链表,循环链表。
单向链表(单链表)是链表的一种,其特点是链表的链接方向是单向的,对链表的访问要通过顺序读取从头部开始;链表是使用指针进行构造的列表;又称为结点列表,因为链表是由一个个结点组装起来的;其中每个结点都有指针成员变量指向列表中的下一个结点;
列表是由结点构成,head指针指向第一个成为表头结点,而终止于最后一个指向nuLL的指针。
//一个链表结构体的声明1
2
3
4typedef struct NODE{
struct NODE *next;
int data;
}Node;
单链表的指向,链表的起始位置我们成为根指针(root),它指向链表的第一个节点。根指针不包含任何数据,它只是一个指针。
Node *root;
下一个节点的访问通过root->next来实现
1 | int InitNodeList(Node **node) |
注:初始化时,这里的指针为二级指针,因为我们想要获取链表的根指针,如果只传入一级指针,malloc后得到的为分配内存的首地址给node,而不能被主函数中指针变量root得到这个地址。
1 | /* |
1 | int LengthNodeList(Node *root) |
1 | void DestroyNodeList(Node *root) |
1 | int main(void) |
双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。
1)在数据结构中具有双向指针 2)插入、删除数据的时候需要考虑前后的方向的操作
一个链表结构体的声明1
2
3
4
5typedef struct NODE{
struct NODE *prev;
struct NODE *next;
int data;
}Node;
1 | int InitNodeList(Node **node) |
2.插入数据:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50int InsertNodeList(register Node *root,int data)
{
register Node *this;
register Node *next;
register Node *newnode;
for(this = root;(next = this->next)!= NULL; this =next)
{
if(next->data == data)
{
return 0;
}
if(next->data > data)
{
break;
}
}
/*新节点分配内存空间*/
newnode = (Node *)malloc(sizeof(Node));
if(newnode == NULL)
{
return -1;
}
/*新节点插入链表*/
newnode->data = data;
/*插入新节点*/
newnode->next = next;
this->next = newnode;
if(this != root)
{
newnode->prev = this;
}
else
{
newnode->prev = NULL;
}
if(next != NULL)
{
next->prev = newnode;
}
else
{
root->prev = newnode;
}
return 1;
}
1 | int LengthNodeList(Node *root) |
1 | void DestroyNodeList(Node *root) |
1 | int main(void) |
我们使用数组时,它是存储于内存中连续的位置上,它所需要的内存在编译时就被分配,使用起来比较方便,但是也存在几个缺点。
1)假如一个数组无限大,而我们只需要很少的几个字节,这就会造成内存浪费,增加了内存的开销。
2)我们我们访问超过数组下标的内容时,造成数组越界,有可能产生一个异常值,从而导致失败。
针对这两点,动态内存分配的处理就相对比较适用。动态内存分配中使用的几个关键字
malloc和free,这两个是成对出现的,malloc用来申请内存空间,free用来释放内存。
malloc从内存池中提取一块合适的内存,并向该程序返回一个指向这块内存的指针。这块内存没有初始化。
当内存不再使用时,调用free把这块内存归还给内存池。
void malloc(size_t size);
void free(void pointer);
malloc的参数是需要分配的内存字节数。
malloc分配的是一块连续的内存。如果请求分配的是100个字节,那么实际分配的就是100个连续的字节,不会分开于两块或多块不同的内存。
如果内存池为空,或者申请内存不成功,malloc就会返回一个NULL指针。如果需要对分配的内存进行检查,可以通过与NULL指针进行比较来判断内存是否分配成功。
free的参数必须是从malloc、calloc或relloc返回的值,或者是NULL。传递NULL指针不会产生任何效果。
例:1
2
3
4
5
6
7
8
9
10
11int *a;
...
a = malloc(100); //申请内存空间
memset(a,0x0,100); //清空内存
if (a == NULL) //判断内存是否申请成功
{
printf(“out of memory!\n”);
return ;
}
free(a); //释放内存
void calloc(size_t num_elements, size_t element_size);
void realloc(void ptr,size_t new_size);
calloc用于内存的申请。
calloc与malloc之间的主要区别是calloc在返回指向内存的指针之前把它初始化为0。但是如果你的程序知识想把一些值存储到数组中,那么这个初始化过程纯属浪费时间。
relloc函数用于修改一个原先已经分配的内存块大小。使用这个函数,你可以使一块内存扩大或缩小。如果它用于扩大一个内存块,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面,新的内存并未以任何方式进行初始化。该内存块尾部的部分内存便被拿掉,剩余部分内存的原先内容依然被保留。
如果realloc函数的第1个参数是NULL,那么它的行为就和malloc一样。
当动态分配的内存不再需要使用时,它应该被释放。如果内存不释放,将会引起内存泄漏。内存泄漏可以一点点的榨干可用内存,从而导致程序崩溃。
在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate data type)的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。
结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,叫做结构。
结构体的定义如下所示,struct为结构体关键字,tag为结构体的标志,member-list为结构体成员列表,其必须列出其所有成员;variable-list为此结构体声明的变量。
struct tag {
member-list
} variable-list ;
1 | //此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c |
1 | //同上声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c |
声明结构体时可以使用的另一种良好技巧是用typedef创建一种新的类型。
typedef struct{
int a;
char b;
double c;
}s3;
这个技巧和标签的效果几乎完全相同,区别在于s3现在是个类型名而不是个结构标签。以后的声明如下:
s3 x;
s3 y[20];
结构体的成员可以是标量、数组、指针甚至是其他结构体。
typedef struct{
int a;
char b;
double c:
s3 d;
s3 e[10];
}s4;
结构体成员通过’.’操作符来访问的。
例:
s4 comp;
访问成员a, comp.a;
访问成员d中的a, comp.d.a;
访问成员e中第5个元素的成员a,comp.e[4].a;
如果拥有一个指向结构体的指针,那么我们访问结构体成员需要用’->’操作符来访问。
s4 *comp;
访问成员a, comp->a;
访问成员d中的a, comp->d.a;
访问成员e中第5个元素的成员a,comp->e[4].a;
“联合”是一种特殊的类,也是一种构造类型的数据结构。在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的。 这是一个非常特殊的地方,也是联合的特征。另外,同struct一样,联合默认访问权限也是公有的,并且,也具有成员函数。
联合使用关键字union来定义。
联合声明跟结构体类似。
union {
数据成员;
}变量;
union{
int a;
float b;
}message;
在一个32位的机器上,变量message只占用内存中最大的变量类型的大小,这里int跟float占用内存相同,因此变量message这里只占用内存中一个32位的字。
如果a被访问,这个字就作为int型访问;
如果b被访问,这个字就作为float型访问。1
2
3
4message.b = 3.14;
message.a = 10;
printf(“%d\n”,message.a);
printf(“%f\n”,message.b);
由于最后一个访问的是a,因此a打印的值为10,b的值为0.000000;反之相同
通常我们把union和struct结合起来使用,这样更加方便我们的程序开发。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40typedef struct{
int age;
int height;
}class;
typedef struct{
int year;
char class_name[10];
enum {class1,class2} type;
union{
class first_student;
class second_student;
}data;
}message;
int main(void)
{
message msg;
int y;
int z;
/*msg相关变量赋值*/
msg.data.first_student.age = 10;
msg.data.first_student.height = 170;
msg.type = class1;
if(msg.type == class1)
{
/*union一次只能访问一个成员*/
y = msg.data.first_student.age;
z = msg.data.first_student.height;
}
else
{
y = msg.data.second_student.age;
z = msg.data.second_student.height;
}
printf("%d\n",y); //打印10
printf("%d\n",z); //打印170
return 0;
}
1.结构体中,不同类型的值可以存储在一起,成员可以是标量、数组、指针或结构体
2.不同的二结构体声明即使它们的成员列表相同也被认为是不同的类型,typedef可以用于解决此问题
3.联合的所有成员都存储于同一个内存位置,通过访问不同类型的联合成员,内存中相同的位组合可以被解释为不同的东西。
字符串是一种重要的数据类型,但是C语言并没有显式的字符串数据类型,因此字符串以字符串常量的形式出现或存储于字符数组中。字符串常量很适用于那些程序不会对它们进行修改的字符串。所有其他字符串都必须存储于字符数组或动态分配的内存中。本次主要描述处理字符串和字符的库函数。
字符串就是一串零个或多个字符,并且以一个位模式为全0的NUL字节结尾。因此,字符串所包含的字符内部不能出现NUL字节。
字符、字符串处理需要引入string.h这个头文件
#include <string.h>
API: strlen
原型:size_t strlen(char const *string)
返回值:string字符串的长度,不包括结束符\0
作用:strlen返回一个类型为sizt_t的值,属于一个无符号整数类型。即为传入字符串的长度
用法:1
2
3const char buf[20] = "hello";
int i = strlen(buf);
printf("i = %d\n",i);
这里i打印值为5,即为字符串hello的长度,不包含结束符。
尝试模仿strlen的写法:1
2
3
4
5
6
7
8
9size_t my_strlen(const char* string)
{
size_t length = 0;
while(*string++ != '\0')
{
length += 1;
}
return length;
}
API:strcpy
原型:char strcpy(char dst,char const *src)
返回值:返回指向dst字符串的指针
作用:这个函数把参数src字符串复制到dst参数。如果参数src和dst在内存中出现重叠,其结果是未定义的。dst必须是一个字符数组或者是一个指向动态分配内存的数组的指针。
src赋值给dst,dst以前的内容将会被覆盖丢失.如果src的长度超过了dst数组的长度,执行时会crash
用法:1
2
3
4char buf[30] = "hello world";
strcpy(buf,"message");
printf("%s\n",buf);
输出内容为 message
模仿strcpy的写法:1
2
3
4
5
6
7
8
9
10
11
12
13char * my_strcpy(char *dst, const char * src)
{
char *ret = dst;
if((dst == NULL) || (src == NULL))
{
return NULL;
}
while(*src != '\0')
{
*dst++ = *src++;
}
return dst;
}
API:strncpy
原型:char strncpy(char dst, char const *src, size_t len);
返回值:返回指向dst字符串的指针
作用:把src字符串的字符复制到dst数组中。它总是正好向dst写入len个字符。如果strlen(src)的值小于len,dst数组会用额外的NUL字节填充到len长度。如果strlne(src)的值大于或等于len,那么只有len 个字符被复制到dst中。
用法:1
2
3
4
5char buf[30] = "hello world";
strncpy(buf,"A message",5);
printf("%s\n",buf); //输出A mes world,长度为5,其余的不拷贝
strncpy(buf,"A message",20);
printf("%s\n",buf); //输出A message
模仿strncpy的写法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18char * my_strncpy(char *dst, const char * src ,int len)
{
char *ret = dst;
size_t i;
if((dst == NULL) || (src == NULL))
{
return NULL;
}
for (i = 0; i < len && src[i] != '\0'; i++)
{
dst[i] = src[i];
}
for (; i < len; i++)
{
dst[i] = '\0';
}
return dst;
}
API: strcat
原型:char strcat(char dst, const char *src);
返回:返回指向dst字符串的指针
作用:把src的字符串添加(连接)到dst字符串后面。这里要求dst原先已经包含了一个字符串(可以为空字符串)。它找到这个字符串的末尾。并把src字符串的一份拷贝添加到这个位置。如果src和dst的位置发生重叠,其结果是未定义的。
用法:1
2
3char buf[20] = “hello”;
strcat(buf,”world”);
printf(“%s\n”,buf); //输出helloworld
模仿strcat的写法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18char * my_stcat(char *dst, const char * src)
{
char *ret = dst;
if((dst == NULL) || (src == NULL))
{
return NULL;
}
while(*dst != '\0')
{
dst++;
}
while(*src != '\0')
{
*dst++ = *src++;
}
*dst = '\0';
return dst;
}
API: strncat
原型:char strncat(char dst, const char *src,size_t len);
返回值:指向dst的指针
作用:从src中复制长度为len个字符到dst中
用法:1
2
3
4
5char buf[20] = “hello”;
strncat(buf,”world”,2);
printf(“%s\n”,buf); //输出hellowo
strncat(buf,”world”,10);
printf(“%s\n”,buf); //输出helloworld,超过部分遇到\0结束符,不再进行连接
模拟strncat:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19char * my_stncat(char *dst, const char * src,size_t len)
{
char *ret = dst;
size_t i;
if((dst == NULL) || (src == NULL))
{
return NULL;
}
while(*dst != '\0')
{
dst++;
}
while(len && (*dst++ = *src++) != '\0')
{
len--;
}
*dst = '\0';
return dst;
}
API: strcmp
原型:int strcmp(const char s1, const char s2)
返回值:0表示两个字符串相同,其余表示两个字符串不同
作用:对两个字符串s1\s2逐个字符进行比较,直到发现不匹配为止
用法:1
2
3
4
5
6char buf[20] = "hello";
int ret = 0;
ret = strcmp(buf,"hello”);
printf("%d\n",ret);//输出0,两个字符串相同
ret = strcmp(buf,”hello world");
printf("%d\n",ret);//输出-32,两个字符串不同
模仿strcmp:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15size_t my_strcmp(const char *s1, const char * s2)
{
size_t ret = -1;
if ((s1 == NULL) || (s2 == NULL))
{
return ret;
}
while(!(ret = *(unsigned char*)s1 - *(unsigned char *)s2) && *s2)
{
++s1;
++s2;
}
return ret;
}
API: strncmp
原型:int strcmp(const char s1, const char s2,size_t len)
返回值:0表示两个字符串相同,其余表示两个字符串不同
作用:对两个字符串s1\s2逐个字符进行比较,直到发现不匹配为止
用法:1
2
3
4
5
6
7char buf[20] = "hello";
int ret = 0;
ret = strncmp(buf,"hello",2);
printf("%d\n",ret); //输出0,表示hello中前两个字符he跟hello中前两个相同
ret = strncmp(buf,"hello world",8);
printf("%d\n",ret); //输出-32,表示不相等,即hello world前8个字符,与hello不相等
模仿strncmp:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19size_t my_strncmp(const char *s1, const char * s2,size_t len)
{
size_t ret = -1;
if ((s1 == NULL) || (s2 == NULL))
{
return ret;
}
while(!(ret = *(unsigned char*)s1 - *(unsigned char *)s2) && *s2)
{
if (len <=0)
{
break;
}
++s1;
++s2;
len--;
}
return ret;
}
原型:char strstr(char s1, char s2)
返回值:NULL,或者指向查找到的起始位置的指针
作用:这个函数在s1中查找整个s2第一次出现的起始位置,并返回一个指向该位置的指针。如果s2并没有完整地出现在s1的任何地方,函数将返回一个NULL指针,如果s2是一个空字符串,函数就返回是
用法:1
2
3
4
5
6char buf[20] = "hello";
char *last = NULL;
last = strstr(buf,"he”);
printf("%s\n",last); //打印hello
last = strstr(buf,"hello world");
printf("%s\n",last); //打印null
模拟strstr写法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29char* my_strstr(char *s1, char * s2)
{
if (*s1 == '\0')
{
if(*s2)
{
return NULL;
}
return s1;
}
while(*s1 != '\0')
{
int i =0;
while(1)
{
if(s2[i] == '\0')
{
return s1;
}
if (s2[i] != s1[i])
{
break;
}
i++;
}
s1++;
}
return NULL;
}
主要有memcpy、memmove、memcmp、memchr、memset几个函数
void memcpy(void dst,void const src,size_t length);
void memmove(void dst,void const src,size_t length);
void memcmp(void const s1,void const s2,size_t length);
void memchr(void s1,void const s2,size_t length);
void memset(void dst,int ch,size_t length);
从src的起始位置赋值length个字节到dst的内存起始位置,可以复制任何类型。如果src和dst出现重叠,其结果是未知的1
2
3
4
5char dst[20];
char src[20] = "hello";
memcpy(dst,src,strlen(src));
printf("%d\n",strlen(src));
printf("%s\n",dst );
模拟memcpy:1
2
3
4
5
6
7
8
9
10
11
12
13
14char *my_memcpy(char *dst, char * src,size_t len)
{
char *ret = dst;
if((dst == NULL) && (src == NULL))
{
return NULL;
}
while(len-- >0)
{
*dst++ = *src++;
}
return ret;
}
memmove用于从src拷贝count个字符到dest,如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。
用法:
char s[]=”Golden Global View”;
memmove(s,s+7,strlen(s)+1-7);
printf(“%s”,s);
程序输出结果:Global View
*注意:这里的拷贝长度strlen(s)+1-7表示把字符串结尾的’\0’也拷贝进来。
memcmp是比较内存区域buf1和buf2的前count个字节。该函数是按字节比较的。
返回值:
当s1
如:char s1=”abc”;
char s2=”acd”;
int r=memcmp(s1,s2,3);
就是比较s1和s2的前3个字节,第一个字节相等,第二个字节比较中大小已经确定,不必继续比较第三字节了。所以r=-1
从buf所指内存区域的前count个字节查找字符ch。1
2
3
4
5
6
7
8char *s="Hello, Programmers!";
void *p;//因为memchr(,,);return void*p;
p=memchr(s,'P',strlen(s));
//p=(char *)memchr(s,'P',sizeof(s)); //s是一个指向char的指针,而任何指针都是个一个4字节的数,在这里应//该是要说明搜索整个字符串的长度,所以应该用strlen(s)
if(p)
printf("%s",p);
else
printf("Not Found!");
将s所指向的某一块内存中的前n个 字节的内容全部设置为ch指定的ASCII值
通常用来进行内存初始化作用
例如:
char dst[20];
memset(dst,0x0,sizeof(dst)); //把dst指向的大小为20的内存区域值都设置为0.
所谓数组,就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,然后用编号区分他们的变量的集合,这个名字称为数组名,编号称为下标。
特点:只能存放一种类型的数据,如全部是int型或者全部是char型,数组里的数据成为元素。
类型 数组名[元素个数-常量表达式]
(1)数组名的命名方法与变量名相同,遵循标识符命名规则;
(2)数组是用方括号括起来的常量表达式,不能用圆括号;
(3)常量表达式表示数组元素的个数,即数组的长度,数组的下标从0开始,下标的最大值为:
常量表达式-1;
(4)常量表达式中可以包括常量和符号常量,不能包括变量。
可以用赋值语句或输入语句使数组中的元素得到值,但要占用运行时间。可以使数组在运行之前初始化,即在编译阶段使之得到初值。
int buf[5] = {1,2,3,4,5};
这里把buf数组中的5个元素初始化为1,2,3,4,5
这里变量buf称为数组,因为它是一些值的集合,下标和数组名一起使用,用于标示该集合中的某个特定的值。例如buf[0]表示数组buf的第一个元素的值,这里值为1. buf[4]标示数组buf第5个值,这里值为5.
buf[0] //对应的值为1
buf[4] //对应的值为5
数组的下标从0开始计数
某个数组的维数不止1个,我们称这个数组为多维数组
数组[下标][下标]
在引用二维数组时,必须是单个元素,不能是整个数组名。下标可以是一个表达式,但不能是变量。如果下标是一个表达式,注意表达式的值不能超出数组定义的上、下限。
int num[3][5];
创建了一个包含15个元素的矩阵。我们一般把这个理解为3行5列,当然也可以理解为5行3列,为了我们方便记忆,我们看做为3行10列。
(1) 多维数组初始化1
static int num[3][5] ={{1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}};
以上赋值把第一个花括号内的数据赋给第一行元素,第二个花括号内数据赋给第二元素,以此类推,即按行赋值
(2) 可以将所有的数据写在一个花括号内,按数组排列的顺序对各元素赋值。
(3)可以对数组的部分元素赋初值。如:1
static int a[3][5] = {{1},{6},{11}};
以上赋值的结果是:数组第一列的元素分别赋了初值1,6,11,其余元素的值都是0。
(4)如果对二维数组的全部元素都赋初值,则定义数组时对第一维的长度可以不指定,但第二维的长度不能省。
指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。
数组指针:首先它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。
例:
int num1[10];
“[]”的优先级比“”要高,num1先与“[]”结合,构成一个数组的定义,数组名为num1.int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。
int (num2)[10];
在这里“()”的优先级比“[]”高,“”号和num2 构成一个指针的定义,指针变量名为num2,int 修饰的是数组的内容,即数组的每个元素。