前言
- 计算机中所有的数据都必须放在内存中,内存的最小单位是BYTE,对于内存,每个BYTE都有一个唯一不同的编号,我们将内存中字节的编号称为内存地址(Address)或指针(Pointer)。
- 地址编号在32位系统下,是一个4个字节的无符号整数,在64位系统下是一个8个字节的无符号整数。
- C语言中,每一个定义的变量,在内存中都占有一个内存单元,比如int类型占四个字节,char类型占一个字节等等,每个字节都在0~4,294,967,295之间都有一个对应的编号,C语言允许在程序中使用变量的地址,并可以通过地址运算符"&"得到变量的地址。
指针的定义和使用
-
指针的概念:
- 指针是一个变量,其值为另一个变量的地址,该地址指向一块内存空间。
- 指针是一种数据类型,和其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。
-
定义指针变量:
- 一般形式:
datatype *var_name; 或 datatype *var_name = value;
- 其中,*表示这是一个指针变量,var_name表示指针变量的名称,datatype表示该指针变量所指向的数据的类型。如:
double *dp; // 一个 double 型的指针 float *fp; // 一个浮点型的指针 char *ch; // 一个字符型的指针 int *p; // 定义一个可以执行int类型地址的指针变量,名字叫a int a = 1; // 定义一个int类型变量,名字叫a p = &a; // 把a的内存地址赋值给p
- 在定义指针变量p的同时对它进行初始化,并将某变量a的地址赋予它,此时 p就指向了 a。值得注意的是,p需要的一个地址,a 前面必须要加取地址符&,否则是不对的。
int a = 100; int *p = &a;
- * 是一个特殊符号,表明一个变量是指针变量,定义 p 时必须带 * 。而给 p赋值时,因为已经知道了它是一个指针变量,就没必要再带上 * ,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带 * ,给指针变量赋值时不能带
*
。换句话说:* 用在指针变量的定义中,表明这是一个指针变量;使用指针变量时在前面加 * 表示获取指针指向的数据,或者说表示的是指针指向的数据本身。int *p = &a; *p = 10;
- 注意:在同一系统下,指针不管指向什么样类型的变量,地址的大小总是一样的。如下:在64位系统下运行的程序,结果为8, 8, 8
#include <stdio.h> int main() { char *p1; int *p2; long long *p3; printf("%lu, %lu, %lu\n",sizeof(p1), sizeof(p2),sizeof(p3)); return 0; }
-
&取地址运算符:
- &可以取得一个变量在内存当中的地址。
- 寄存器变量不能使用&来取得地址。如:
register int a; // 寄存器变量,这种变量不在内存里面,而在CPU里面,所以每有地址
-
* 与 & :
- 若有一个 int 类型的变量 a,p 是指向它的指针,则 *&a可以理解为 *(&a),&a表示取变量a的地址(等价于p), *(&a)表示取这个地址上的数据(等价于*p),因此,*&a等价于a。
- &*p可以理解为 &(*p),*p表示取p指向地址的数据(等价于a), &(*p)表示数据的地址(等价于&a),因此,&*p等价于p。
-
无类型指针:
- 定义一个指针变量,但不指定它指向具体哪种数据类型,可以通过强制转化将void *转化为其他类型指针,也可以用(void *)将其他类型指针强制转化为void类型指针。
- 指针之间赋值的类型相同,但任何类型的指针都可以赋值给void *
-
NULL指针:
- NULL在C语言里面是一个宏常量,值是0.
- 在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
#include <stdio.h> int main () { int *p = NULL; printf("p的地址是 %p\n", p ); return 0; }
- 在程序中不要出现野指针,但可以出现空指针。
#include <stdio.h> int main () { int *p; // 未初始化过值的指针,叫野指针 *p = 100; return 0; }
-
指向常量的指针与指针常量:
- 指向常量的指针:
const dataTpye *p; // 定义一个指向常量的指针
#include <stdio.h> int main () { int a = 0; const int *p = &a; // p可以指向一个int类型的地址,但不可以用*p的方式修改这个内存的值 *p = 1; // 编译时,此时这行程序出错 printf("%d\n", a); return 0; }
- 指针常量一旦初始化之后,其内容不可改变:
dataTpye *const p; // 定义一个指针常量,p只能指向固定的一个变量的地址
#include <stdio.h> int main () { int a = 0; int b = 10; int *const p = &a; // p指向a的地址,且只能指向a的地址,但可以通过*p读写这个变量的值 p = &b; // 此时编译时,这行会出错 printf("%d\n", a); return 0; }
- C语言中const是有问题的,因为可以通过指针变量间接的修改const常量的值,因此在C语言中用#define常量的时候更多。
/**** 将const常量a=10改为a=0 ****/ #include <stdio.h> int main () { const int a = 10; int *p; p = &a; // 会出现warning,可忽略 //p = (int *)&a; // 强制转换,连warning都消失了 *p = 0; printf("%d\n", a); return 0; }
指针与数组
-
通过指针访问数组元素:
- 单独输出数组的名字就是相当于输出数组首元素的地址。
int a[10] = {1,2,3,4,5,6,7,8,9,10}; int *p // 声明指针变量p p = &a[0] // 指针赋予首元素地址 等价于=> int a[10] = {1,2,3,4,5,6,7,8,9,10}; int *p // 声明指针变量p p = a // 指针赋予首元素地址 等价于=> int a[10] = {1,2,3,4,5,6,7,8,9,10}; int *p = a // 声明指针变量并赋值地址
- 用指针和数组名表示数组元素的值及所在的地址:
int a[10]; int *p = a; 则a[i]的地址表现形式:p + i 或 a + i a[i]的值表现形式:*(p + i) *(a + i) p[i] // 这个前提指针变量必须已经指向数组首地址即p = a
- 程序例子:
/**** 输出结果:1 2 20 4 5 50 7 8 9 10 ****/ #include <stdio.h> int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p1 = a; // 定义指针变量p1,并指向数组a的首地址 int *p2 = &a[3]; // 定义指针变量p2,并指向数组元素a[3]的地址 *(p1 + 2) = 20; // 将20赋值给a[2] *(p2 + 2) = 50; // 将50赋值给a[5] int i; for(i = 0; i < 10; i++) printf("%d ", a[i]); return 0; }
- C语言中所有的数据类型都可以理解为一个一维char的数组,可以用指针转换。
/**** 编译器所处的电脑符合小端对齐, ***** 输出结果:78, 56, 34, 12 ***** 若是大端对齐, *****输出结果:12, 34, 56, 78****/ #include <stdio.h> int main() { int a = 0x12345678; char *p = (char *)&a; printf("%x, %x, %x, %x\n",*p, p[1], p[2], p[3]); return 0; }
- 例子:IP地址<=>整数的转换。
/**** 整数 => IP ***** *****输出结果:58.222.104.177 ****/ #include <stdio.h> int main() { unsigned int a = 987654321; unsigned char *p = (unsigned char *)&a; // 强制转换 printf("%u.%u.%u.%u\n", p[3], p[2], p[1], p[0]); // 编译器处于小端对齐的环境中 return 0; } /**** IP => 整数 ***** *****输出结果:3232237314 ****/ #include <stdio.h> int main() { char a[] = "192.168.7.2"; unsigned int ip = 0; unsigned char *p = (unsigned char *)&ip; // 强制转换,int转换成char数组 int a1, a2, a3, a4; sscanf(a, "%d.%d.%d.%d", &a1, &a2, &a3, &a4); p[0] = a4; p[1] = a3; p[2] = a2; p[3] = a1; printf("%u\n", ip); return 0; }
- 例子:二维数组排序。
/**** 输出结果:1 2 3 4 4 6 7 8 9 10 ****/ #include <stdio.h> int main() { char a[2][5] = {{3,1,8,9,4}, {6,7,4,2,10}}; char *p = (char *)a; // 强制转换,转换为一维char数组 int i,j; for (i = 0; i < 10; i++) { for(j = 1; j < 10 - i; j++) { if(p[j] < p[j - 1]) { char tmp = p[j]; p[j] = p[j-1]; p[j-1] = tmp; } } } for(i = 0; i < 2; i++) { for(j = 0; j < 5; j++) printf("%d ", a[i][j]); } return 0; }
-
数组指针:
- 指向数组的指针常被简称为数组指针。
- 以int类型数组举个例子。如下行语句。因为“ () ”的优先级比“ [ ] ”高,所以“ * ”和 p 构成一个指针的定义,指针变量名为p,而int 修饰的是数组的内容,即数组的每个元素。换句话说,p 是个指针,它指向一个包含10个 int 类型数据的数组,一次它是一个数组指针。数组在这里没有名字,是个匿名数组。
int (*p)[10] = NULL;
- 对于上行语句,指向有10个 int 元素的数组的指针会被初始化为 NULL。当把合适数组的地址分配给它,那么表达式 *p 会获得数组,并且 (*p) [i] 会获得索引值为 i 的数组元素。根据下标运算符的规则,表达式 (*p) [i] 等同于 * ( (*p) + i )。因此,**p 获得数组的第一个元素,其索引值为 0。
- 注意:如果想把一个多维数组传入函数,则必须声明对应的函数参数为数组指针。对于二维数组,数组指针的使用方式如下例子:在初始化赋值后,p 指向数组a的第一个行,在这种情况下,使用 p 获取元素的方式与使用 a 完全一样,即赋值运算 (*p) [0]=1 等效于 p[0][0]=5 和 a[0][0]=1。然而,与数组名称 a 不同的是,指针名称 p 并不代表一个常量地址,如运算 ++p ,它进行了自增运算,这个自增运算会造成存储在数组指针的地址增加一个数组空间大小,在本例中,即增加矩阵一行的空间大小,也就是 3 乘以 int 元素在内存中所占字节数量。
-
/**** 输出结果:1,2,3,4,5,6 ****/ #include <stdio.h> int main() { int a[2][3] = { 0 }; // 2行3列的二维数组,数组名称是指向第一个元素的指针,也就是第一行的指针 int (* p)[3] ; p = a; // p指向的数组a第一行 (*p)[0] = 1; // 将1赋值给第一行的第一个元素 p[0][1] = 2; // 将2赋值给第一行的第二个元素 p[0][2] = 3; // 将3赋值给第一行的最后一个元素 ++p; // 将指针移动到下一行 (*p)[0] = 4; // 将4赋值给第二行的第一个元素 (*p)[1] = 5; // 将5赋值给第二行的第二个元素 p[0][2] = 6; // 将6赋值给第二行的第二个元素 printf("%d, %d, %d, %d, %d, %d\n", a[0][0],a[0][1],a[0][2],a[1][0],a[1][1],a[1][2]); return 0; }
- 对数组指针来说,首先它是一个指针,指向一个数组,也就是说它是指向数组的指针,在32 位系统下永远是占4 个字节,在64位系统占8个字节,至于它指向的数组占多少字节,这个不能够确定,要看具体情况。
- 下面例子中,p1 和 p2 都是数组指针,指向的是整个数组,&a 是整个数组的首地址,a 是数组首元素的首地址,其值相同但意义不同。在C 语言里,赋值符号“ = ”号两边的数据类型必须是相同的,如果不同需要显示或隐式的类型转换。p1 定义的“ = ”号两边的数据类型完全一致;而 p2 这个定义的“ = ”号两边的数据类型不一致,左边的类型是指向整个数组的指针,右边的数据类型是指向单个字符的指针。因此,对于p2这行的定义编译器会给出warning。
-
#include <stdio.h> int main() { int a[10]={1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int (*p1)[10] = &a; // 正确 int (*p2)[10] = a; // 编译器会给出warning printf("%d, %d\n", (*p1)[10], (*p2)[10]); return 0; }
-
指针数组:
- 具有指针类型元素的数组则被称为指针数组。
- 以int类型数组举个例子。如下行语句。因为“ [ ] ”的优先级比“ * ”高,所以p先与“ [ ] ”结合,构成一个数组的定义,数组名为p,而“ int * ”修饰的是数组的内容,即数组的每个元素。换句话说,该数组包含10个指向 int 类型数据的指针,因此它是一个指针数组。
int *p[10];
- 对指针数组来说,首先它是一个数组,数组的元素都是指针,也就是说该数组存储的是指针,数组占多少个字节由数组本身决定。
#include <stdio.h> int main() { char *a[10]; // 定义一个指针数组,每个成员是char *类型,一共10个成员 int *b[10]; // 定义一个指针数组,每个成员是int *类型,一共10个成员 printf("%lu, %lu\n", sizeof(a), sizeof(b)); // 64位系统下编译的,一个指针占8个字节,输出结果:80,80 int i = 0; a = &i; // 错误,数组名不能做左值 b[0] = &i; // 正确,b[0]存放的是指针 printf("%lu, %lu\n", sizeof(b[0]),sizeof(*b[0])); // 输出结果:8,4 return 0; }
/**** 输出结果:a1 = 1, a2 = 2, a3 = 3 ****/ #include <stdio.h> int main() { int *b[10] = { NULL }; // 定义个指针数组,有10个int *类型成员,10个指针均为空 int a1, a2, a3; b[0] = &a1; b[1] = &a2; b[2] = &a3; *b[0] = 1; *b[1] = 2; *b[2] = 3; printf("a1 = %d, a2 = %d, a3 = %d\n",a1, a2, a3); return 0; }
- 指针数组常作为二维数组的一种便捷替代方式,一般情况下,这种数组中的指针会指向动态分配的内存区域。如下处理字符串的例子,第一种方式是将他们存储在二维数组中,但数组行空间大小必须足以存储下可能出现的最长字符串,这种方式会出现短字符串让大部分的行是空的或者有些行根本没用到,但却已经预留了内存,从而导致内存浪费。另一种方式采用指针数组,让指针指向字符串这个对象,然后只给实际存在的对象分配内存,未用到的数组元素为空指针。
/**** 方式1:使用二维数组方式存储字符串****/ char str[100][100] = {"hello", "world","thank","you","I love you"}; /**** 方式2:使用指针数组存储字符串 ***** 只使用了str_p[0],str_p[1],str_p[2],str_p[3],str_p[4] ***** 其余未使用的指针可以在运行时指向另一个字符串****/ char *str_p[100] = {"hello", "world","thank","you","I love you"};
指针运算
-
算术运算:
- 指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分算术运算,例如加法、减法,不支持乘除。
- 指针变量加减运算的结果跟数据类型的长度有关。当一个指针和一个整数量进行算术运算时,整数在执行加法(减法)运算前会根据数据类型进行调整
。
一般这个
“调整”就是把整数值和数据类型占用字节的大小相乘。如下程序:/**** 输出结果:**** &a=0XDE35986B, &b=0XDE35985E, &c=0XDE35984C, &d=0XDE359838, &e=0XDE359828 pa=0XDE35986B, pb=0XDE35985E, pc=0XDE35984C, pd=0XDE359838, pe=0XDE359828 pa=0XDE35986C, pb=0XDE359860, pc=0XDE359850, pd=0XDE359840, pe=0XDE359830 pa=0XDE35986A, pb=0XDE35985C, pc=0XDE359848, pd=0XDE359830, pe=0XDE359820 ********************/ #include <stdio.h> int main() { char a = 'a', *pa = &a; short int b = 0, *pb = &b; int c = 1, *pc = &c; long d = 2, *pd = &d; double e = 3, *pe = &e; /**** 初始值 ****/ printf("&a=%#X, &b=%#X, &c=%#X, &d=%#X, &e=%#X\n", &a, &b, &c, &d, &e); printf("pa=%#X, pb=%#X, pc=%#X, pd=%#X, pe=%#X\n", pa, pb, pc, pd, pe); /**** 加法运算 ****/ pa++; pb++; pc++; pd++; pe++; printf("pa=%#X, pb=%#X, pc=%#X, pd=%#X, pe=%#X\n", pa, pb, pc, pd, pe); /**** 减法运算 ****/ pa -= 2; pb -=2; pc -=2, pd -= 2; pe -= 2; printf("pa=%#X, pb=%#X, pc=%#X, pd=%#X, pe=%#X\n", pa, pb, pc, pd, pe); return 0; }
- 注意:不要对指向普通变量的指针进行加减运算,因为C语言并没有规定变量的存储方式,如果连续定义多个变量,它们有可能是挨着的,也有可能是分散的。一般用在数组中,如下程序:
/**** 输出结果:**** 0 0 0 3 0 1 0 0 0 0 ********************/ #include <stdio.h> int main() { int a[10] = { 0 }; int *p =a; // 指向数组a的首地址 p += 5; // 指向数组a下标为5的地址 *p = 1; // 1赋值给a[5] p -= 2; // 指向数组a下标为3的地址 *p = 3; // 3赋值给a[3] int i; for(i = 0; i < 10; i++) printf("%d ",a[i]); return 0; }
-
比较运算:
- 当对指针变量进行比较运算时,比较的是指针变量本身的值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据,否则就指向不同的数据。
- 只有当2个指针指向同一个数组中的元素时,才能进行关系运算。如下:
当指针p和指针q指向同一个数组中的元素时: if(p < q) // 当p所指的元素在q所指的元素之前时,表达式的值为1,反之为0 if(P > q) // 当p所指的元素在q所指的元素之后时,表达式的值为1,反之为0 if(p == q) // 当p和q指向同一元素时,表达式的值为1,反之为0。 if(p != q) // 当p和q不指向同一元素时,表达式的值为1,反之为0。
二级指针
-
定义和使用:
- 指针可以指向一份普通类型的数据,例如 int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double *、char * 等。如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。
- 如下例子:对于 int 类型的变量 a,p是指向 a 的指针变量,pp 又是指向 p 的指针变量。
int a =10; int *p = &a; int **pp = &p;
- 例子:二级指针的使用。
/**** 输出结果:a = 10 ****/ #include <stdio.h> int main() { int a = 0; int *p = &a; int **pp; // 二级指针,指向指针的指针 pp = &p; **pp = 10; printf("a = %d\n", a); return 0; }
- 例子:二级指针与指针数组。
/**** 输出结果:0, 8 ****/ #include <stdio.h> int main() { int *a[10]; // 定义个指针数组,元素包括10个int *类型的指针 int **pp = a; // 定义个二级指针,指向指针数组 pp[0] = NULL; printf("%lu, %lu\n", a[0], sizeof(pp[0])); return 0; }
-
多级指针:
- C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号*。形式如下:
int a =10; int *p = &a; int **pp = &p; int ***ppp = &pp; // 三级指针 int ****pppp = &ppp // 四级指针
- 在实际开发过程中常用一级和二级指针,多级指针一般避免使用,会导致程序很复杂。
指针与函数
-
指针变量作为函数的参数:
- 函数的参数可以使指针类型,它的作用是将一个变量的地址传送给另一个函数。如:
void test_demo(int *a, int *b); // 声明一个函数,2个形参是int *类型
- C语言中如果想通过函数内部修改外部实参的值,只能给函数传递实参的地址来间接的修改实参的值。如下2个程序:
/**** 输出结果:a = 1, b = 2 **** ***** 值没有改变,因为形参值的改变不会影响实参的值 ****/ #include <stdio.h> void change_demo(int a, int b) { int tmp = a; a = b; b = tmp; } int main() { int a = 1; int b = 2; change_demo(a, b); printf("a = %d, b = %d\n",a, b); return 0; }
/**** 输出结果:a = 2, b = 1 **** ***** 利用指针传递实参地址,值发生改变 ****/ #include <stdio.h> void change_demo(int *a, int *b) { int tmp = *a; *a = *b; *b = tmp; } int main() { int a = 1; int b = 2; change_demo(&a, &b); printf("a = %d, b = %d\n",a, b); return 0; }
-
数组名作为函数参数:
- 当数组名作为函数的形参时,C语言将数组名解释为指针变量名。
/**** 输出结果:1 0 0 0 0 0 0 0 0 0 ****/ #include <stdio.h> void test_demo(int *a) // 还可以写成void test_demo(int a[]) 或 void test_demo(int a[10]) { a[0] = 1; } int main() { int a[10] = { 0 }; test_demo(a); int i; for(i = 0; i < 10; i++) printf("%d ",a[i]); return 0; }
- 如果一个数组作为函数的参数,那么数组的成员数量在函数内部是不可见的,在传递一个数组时,可以同时提供另一个参数,标明这个数组有几个成员数量。如果函数参数是字符串,则不需要,因为字符串总是以'/0'结束。如:
/**** 输出结果:1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ****/ #include <stdio.h> void array_demo(int n, int *a) { int i; for(i = 0; i < n; i++) printf("%d, ", a[i]); } int main() { int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; array_demo(sizeof(a)/sizeof(a[0]), a); return 0; }
- 当这个数组是指针数组时:
/**** 输出结果:hello world! love you ****/ #include <stdio.h> int P_array_demo(int n, char **p) // 或int P_array_demo(int n, char *p[3]),int P_array_demo(int n, char *p[]) { int i; for(i = 0; i < n; i++) printf("%s ", p[i]); // p[i]为char *类型 return 0; } int main() { char *a[4]; char a1[] = "hello"; char a2[] = "world!"; char a3[] = "love"; char a4[] = "you"; a[0] = a1; a[1] = a2; a[2] = a3; a[3] = a4; P_array_demo(sizeof(a)/sizeof(a[0]), a); return 0; }
-
指针数组作为main函数的形参:
- 形式如下:argc参数标明argv这个数组的成员数量,代表命令行参数的数量,程序名字本身也算一个参数;argv是命令行参数的字符串数组,每个成员都是char *类型。
int main(int argc, char *argv[]);
- main函数是操作系统调用的,所以main函数的参数功能是得到命令行的参数。
/**** 可执行文件名为a,在命令行输入a hello world **** ***** 输出结果:a hello world ****/ #include <stdio.h> int main(int argc, char **argv) // argv是一个指针数组,成员是char *类型,argc代表这个数组有多少成员 { int i; int j = argc; for(i = 0; i < j; i++) printf("%s ", argv[i]); return 0; }
- 例子:
/**** 可执行文件名为b,在命令行输入./b 1 2 **** ***** 输出结果:3 ****/ #include <stdio.h> #include <stdlib.h> int main(int argc, char **argv) // argv是一个指针数组,成员是char *类型,argc代表这个数组有多少成员 { if(argc <= 2) { printf("缺少参数\n"); return 0; } int a = atoi(argv[1]); int b = atoi(argv[2]); printf("%d\n", a + b); return 0; }
-
指针作为函数的返回值:
- C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。如下例子:比较字符串长度。
/**** 输出结果:longer string is world! ****/ #include <stdio.h> #include <string.h> char *str_demo(char *str1, char *str2) { if(strlen(str1) >= strlen(str2)) return str1; else return str2; } int main() { char str1[30] = "hello"; char str2[30] = "world!"; char *str; str = str_demo(str1, str2); printf("longer string is %s\n", str); return 0; }
- 注意:用指针作为函数返回值时,函数运行结束后会销毁在它内部定义的所有局部数据,函数返回的指针应尽量不要指向这些数据,否则,会出现数据错误。如下:
#include <stdio.h> int *test_demo() { int i = 10; return &i; } int main() { int i; int *p = test_demo; i = *p; printf("%d\n", i); // 输出奇怪的数 return 0; }
memset,mencpy,menmove函数
-
memset函数:
- memset主要功能是将指定内存区域置空。需要包含头文件string.h。
- 函数声明:
void *memset(void *str, int c, size_t n) // 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符
- 参数说明:str: 指向要填充的内存块;c:要被设置的值,该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式;n:要被设置为该值的字节数。
- 例子:
/**** 将指定内存区域置空 **** ***** 输出结果:0 0 0 0 0 0 0 0 0 0 ****/ #include <stdio.h> #include <string.h> int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; memset(a, 0, sizeof(a)); // 第一个参数时内存首地址,第二个参数是0,第三个参数是这块内存的大小 int i; for(i = 0; i < 10; i++) printf("%d ", a[1]); return 0; } /**** 将指定内存区域设置指定字符 **** ***** 输出结果:***** world ! ****/ #include <stdio.h> #include <string.h> int main () { char str[100] = "hello world !"; memset(str,'*',5); printf("%s\n", str); return(0); }
-
memcpy函数:
- memcpy主要功能是实现内存拷贝。需要包含头文件string.h。
- 函数声明:
void *memcpy(void *dest, const void *src, size_t n) // 从存储区 src 复制 n 个字符到存储区 dest
- 参数说明:dest:目标内存地址,类型强制转换为 void * 指针;src:源内存地址,类型强制转换为 void * 指针;n:拷贝的字节数。
- 注意:使用memcpy的时候,一定要确保内存没有重叠区域。
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; /**** 从a[0]开始拷贝20个字节(int类型,4x5,即a[0]到a[4])到a[3]-a[7]区域,**** ***** 此时会出现a[3],a[4]重叠区域,有可能还没拷贝数据就被覆盖了,应避免内存重叠区域 ****/ memcpy(&a[3], &a[0], 20);
- 例子:
/**** 将数组a的内容复制到数组b中 **** ***** 输出结果:1 2 3 4 5 6 7 8 9 10 ****/ #include <stdio.h> #include <string.h> int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int b[10] = { 0 }; memcpy(b, a, sizeof(a)); int i; for(i = 0; i < 10; i++) printf("%d ", b[i]); return 0; } /**** 将a中第6个字符开始的5个连续字符复制到b中: **** ***** 输出结果:world ****/ #include <stdio.h> #include<string.h> int main() { char a[] = "hello world !"; char b[20]; memcpy(b, a+6, 5); // 或者memcpy(b, a+6*sizeof(char), 5*sizeof(char)); b[5]='\0'; printf("%s", b); return 0; }
-
memmove函数:
- memmove主要功能是实现内存移动。需要包含头文件string.h。
- 函数声明:
void *memmove(void *dest, const void *src, size_t n) // 从 src 复制 n 个字符到 dest
- 注意:参数和memcpy一样,但是在重叠内存块这方面,memmove() 是比 memcpy() 更安全的方法。如果目标区域和源区域有重叠的话,memmove() 能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中,复制后源区域的内容会被更改。如果目标区域与源区域没有重叠,则和 memcpy() 函数功能相同。
字符指针与字符串
-
指针和字符串:
- C语言中没有特定的字符串类型,通常是将字符串放在一个字符数组中,大多数字符串操作其实就是指针操作。如:
#include <stdio.h> int main() { char a[100] = "hello world"; char *p = a; *p = 'a'; p[1] = 'b'; printf("%s\n",a); // 输出:abllo world return 0; }
- 例子:指针实现字符串元素反转。
/**** 输出结果:gfedcba ****/ #include <stdio.h> #include <string.h> int main() { char a[100] = "abcdefg"; char *p = a; char *p1 = p; // 指针p指向首元素地址 int len = strlen(a); p1 += len - 1; // 指针p1指向'g'地址 while(p < p1) { char tmp = *p; *p = *p1; *p1 = tmp; p++; p1--; } printf("%s\n",a); return 0; }
-
函数的参数为char *:
- 函数的参数是字符串:
void test(char *p);
- 例子:
/**** 输出结果:abcdef *************** abc1ef ****/ #include <stdio.h> void test_demo(char *a) { printf("%s\n", a); a[3] = '1'; } int main() { char a[] = "abcdef"; test_demo(a); printf("%s\n",a); return 0; }
- 当函数的参数是字符串时,如果函数内部并不改变实参的值,那么就把形参定义为const char *。
/**** 输出结果:abcdef *************** abc1ef ****/ #include <stdio.h> void test_demo(const char *a) { printf("%s\n", a); a[3] = '1'; // 编译时这行代码会出错 } int main() { char a[] = "abcdef"; test_demo(a); printf("%s\n",a); return 0; }