一.指针的相关概念
1.1 指针变量
指针是一个变量,存放的是一个地址,该地址指向一块内存空间。
例:
int a = ; int *p = &a; // 定义一个指针变量p,&符号可以取得一个变量在内存当前中的地址。 *p = ; // 修改指针所指的内存数据为5
1.2 无类型指针
定义一个指针变量,但不指定它指向具体哪种数据类型,可以通过强制转化将void *转化为其它类型指针,也可以用(void *)将其它类型指针强制转化为void *类型指针。
1.3 NULL
NULL在C语言中的定义为(void *)0,空指针就是指向了NULL的指针变量。
int *p = &a;
p = NULL; // p就是一个空指针
1.4 野指针
野指针就是没有指向任何有效地址的指针变量。在代码中应当尽量避免出现野指针,如果一个指针不能确定指向任何一个变量的地址,那么请将这个指针变成空指针。
二.使用指针时的一些注意点
2.1 指针的兼容性
指针之前赋值比普通数据类型赋值检查更为严格,如:不可以将一个double * 赋值给一个int。且指针变量只能存放地址,也不能将一个int类型变量直接赋值给一个指针。
2.2 常量指针与指针常量
记忆方法:* (指针)和 const(常量) 谁在前先读谁 ;*象征着地址,const象征着内容;谁在前面谁就不允许改变。
int a =;
int b = ;
int c = ;
int const *p1 = &b;//const 在前,定义为常量指针
int *const p2 = &c;//*在前,定义为指针常量
常量指针p1:指向的地址可以变,但内容不可以重新赋值,内容的改变只能通过修改地址指向后变换。
p1 = &a是正确的,但 *p1 = a是错误的。
指针常量p2:指向的地址不可以重新赋值,但内容可以改变,必须初始化,地址跟随一生。
p2= &a是错误的,而*p2 = a 是正确的。
2.3 指针所占的位数
指针所占的位数不会因类型不同而不同,如int *p与double *q 在同一操作系统下所占的位数始终是相同的,只跟操作系统的位数(x64/x86)相关。
2.4 指针与数组之间的关系
在C语言中数组的名字就是数组的地址,数组的地址也等价于数组第一个元素的地址。
例:
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数 void main() { int a[] = {,,};
int *p = a;
int *q = &a[];
printf("变量p存放的地址为:%p\n变量q存放的地址为:%p\n",p,q); system("pause");
}
也可以通过指针来遍历当前数组元素:
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数 void main() { int a[] = {,,};
int *p = a;
//int *q = &a[0];
//printf("变量p存放的地址为:%p\n变量q存放的地址为:%p\n",p,q);
for (int i = ; i < ; i++) {
printf("第%d个元素为:%d\n",i,p[i]);
}
system("pause");
}
三.指针的其它用法
3.1 指针的运算
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数 // 指针的运算
void point_test_1(); // 清除数组中的值(p的值发生改变)
void clearArrayValueByPoint(); // 清除数组中的值(p的值不发生改变)
void clearArrayValueByPoint2(); // 使用指针来进行冒泡排序
void sortByPoint(); // 任何一种数据类型都是char的数组
void point_test_2();
void point_test_3(); // 表示IP地址 192.168.111.123
void ipAddressByPoint(); void main() { ipAddressByPoint();
system("pause");
} // 指针的运算
void point_test_1() { int *p1;
char *p2;
int a1 = ;
char a2 = ;
p1 = &a1;
p2 = &a2;
printf("%p,%p\n", p1, p2); p1++; // 位移了4个字节,4个sizeof(int)
p2++; // 位移了1个字节,1个sizeof(char)
printf("%p,%p\n", p1, p2); p1 += ; // 位移了4*sizeof(int) = 16
p2 += ; // 位移了4*sizeof(char) = 4
printf("%p,%p\n", p1, p2); p1 -= ; // 向前移动了2*sizeof(int)
p2 -= ; // 向前移动了2*sizeof(char)
printf("%p,%p\n", p1, p2); } // 清除数组中的值(p的值发生改变)
void clearArrayValueByPoint() { int array[] = {,,,,,,,,,}; int *p = array; for (int i = ; i < sizeof(array) / sizeof(int); i++) {
*p = ;
p++;
} for (int i = ; i < sizeof(array) / sizeof(int); i++) {
printf("array[%d]=%d\n", i, array[i]);
}
} // 清除数组中的值(p的值不发生改变)
void clearArrayValueByPoint2() { int array[] = { , , , , , , , , , }; int *p = array; for (int i = ; i < sizeof(array) / sizeof(int); i++) {
*(p + i) = ; // 循环完成之后,p仍然指向首元素地址
} for (int i = ; i < sizeof(array) / sizeof(int); i++) {
printf("array[%d]=%d\n", i, p[i]); // 因为此时p仍然指向首元素地址,所以可以使用p[i]来输出各个元素
}
} // 使用指针来进行冒泡排序
void sortByPoint() { int array[][] = { { , , , }, { , , , }, {,,,} };
int *p = array; // array可理解成里面有12个元素的array[12],任何一个多维数组都可以理解成一个一维数组 // 排序
for (int i = ; i < ; i++) { for (int j = ; j < -i;j++) {
if (p[j] < p[j-]) {
int tmp = p[j];
p[j] = p[j - ];
p[j - ] = tmp;
}
}
} // 输出
for (int i = ; i < ; i++) {
for (int j = ; j < ; j++) {
printf("array[%d][%d]=%d\t",i,j,array[i][j]);
}
printf("\n");
} } // 任何一种数据类型都是char的数组
void point_test_2() { int a = 0x12345678;
unsigned char *p = (char *)&a;
printf("%x, %x, %x, %x\n",p[],p[],p[],p[]); } void point_test_3() { int a = 0x12345678;
unsigned char *p = (char *)&a;
printf("%x, %x, %x, %x\n", p[], p[], p[], p[]);
p[] = ;
printf("%x\n",a); } // 表示IP地址 192.168.111.123
void ipAddressByPoint() { unsigned int a = ;
unsigned char *p = (char *)&a;
p[] = ;
p[] = ;
p[] = ;
p[] = ;
printf("%u\n",a); }
3.2 多级指针
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数 // 多级指针
void multiPoint(); // 二级指针
void multiPoint1(); // 三级指针
void multiPoint2(); void main() { multiPoint2();
system("pause");
} // 多级指针
void multiPoint() { int *a[] = { };
int **p = a;
int array[] = { , , , , , , , , , }; for (int i = ; i < ; i++) {
a[i] = &array[i]; // 将指针数组中的每一个指针指向数组中的每一个元素
} for (int i = ; i < ; i++) {
printf("array[%d]=%d\n", i, *p[i]); // 取出每个指针对应的数组内容
}
} // 二级指针
void multiPoint1() { int a = ;
int *p = &a;
int **q = &p; // 二级指针不能直接指向int的地址,而是指向一个int*的地址
// 修改a的值
**q = ;
printf("a=%d\n",a);
} // 三级指针
void multiPoint2() { int a = ;
int *p = &a;
int **q = &p; // 二级指针不能直接指向int的地址,而是指向一个int*的地址
int ***z = &q;
// 修改a的值
***z = ;
printf("a=%d\n", a);
}
3.3 指针变量当做函数的参数
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数 // 将指针变量用作函数的参数
void pointAsFunctionArgus(int *a); // 交换两个数的值
void myswap(int *a1, int *a2); void main() { //int a = 10;
//// 将a的值+1
//pointAsFunctionArgus(&a);
//printf("a=%d\n",a); int a = ;
int b = ;
// 交换a,b的值
myswap(&a,&b);
printf("a=%d\nb=%d\n",a,b); system("pause");
} // 交换两个数的值
void myswap(int *a1, int *a2) { int tmp = *a1;
*a1 = *a2;
*a2 = tmp; } // 将指针变量用作函数的参数
void pointAsFunctionArgus(int *a) {
(*a)++;
}
3.4 数组名作为函数参数
当数组名作为函数参数时,C语言将数组名解释为指针,以下三种写法等价:
int func(int array[10]);
int func(int array[]);
int func(int *array)
如果数组名作为函数的参数,那么这个就不是数组了,而是一个指针变量。
在C语言中,如果把数组名作为函数的参数,那么在函数内部就不知道这个数组的元素个数了,需要再增加一个参数来标明这个数组的大小。
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数 // 把数组名作为函数参数
void arrayNameAsFunctionArgus(int array[]); // 将数组内容输出
void myprint(const int *a, unsigned int n); // 冒泡排序
void bubble(int *a, unsigned int n); void main() { //int array[10] = {1,2,3,4,5,6,7,8,9,10};
//arrayNameAsFunctionArgus(array); int array[][] = { { , , , }, { , , , }, {,,,} };
bubble(array,sizeof(array) / sizeof(int));
myprint(array, sizeof(array) / sizeof(int)); system("pause");
} // 冒泡排序
void bubble(int *a,unsigned int n) { for (int i = ; i < n; i++) {
for (int j = ; j < n - i; j++) {
if (a[j] < a[j-]) {
int tmp = a[j];
a[j] = a[j - ];
a[j - ] = tmp;
}
}
}
} // 将数组内容输出
void myprint(const int *a, unsigned int n) { for (int i = ; i < n; i++) {
printf("%d\n",a[i]);
} } // 把数组名作为函数参数,当数组名作为函数参数时,C语言将数组名解释为指针(下列三种写法等价)
//void arrayNameAsFunctionArgus(int array[])
//void arrayNameAsFunctionArgus(int *array)
void arrayNameAsFunctionArgus(int array[]) {
*array = ; // 这种写法很危险!!!
array += ; // 1.使用者可以修改原始数组里的值,2.若使用者对指针进行运算,还会发生越界!!!正确写法: func(const int array[])
// const 保护形参的值不被改变 for (int i = ; i < ; i++) {
printf("%d\n",array[i]);
}
}
四.memset、memcpy,memmove函数
4.1 memset函数
#include <string.h>
void *memset( void *buffer, int ch, size_t count );
功能:设置一块内存区域。主要作用:把一块内存重新设置为0
参数说明:第一个参数是内存首地址,第二个参数是要设置的值,第三个参数是这块内存的大小,单位:字节。
4.2 memcpy函数
#include <string.h>
void *memcpy( void *to, const void *from, size_t count );
功能:内存拷贝。使用memcpy的时候要注意内存区域不能重叠。
参数说明:第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数。
4.3 memmove函数
#include <string.h>
void *memmove( void *to, const void *from, size_t count );
功能:内存移动,参数与memcpy一致。
参数说明:第一个参数是目标内存首地址,第二个参数是源内存首地址,第三个参数是拷贝字节数。
示例代码:
#include <stdio.h> // 这个头文件在系统目录下
#include <stdlib.h> // 使用了system函数
#include <string.h> // 使用memset、memcpy、memmove函数 // memset函数的使用
void memsetDemo(); // memcpy函数的使用(使用memcpy的时候需要注意内存区域不能重叠)
void memcpyDemo(); // memmove函数的使用
void memmoveDemo(); void main() { memmoveDemo();
system("pause");
} // memmove函数的使用
void memmoveDemo() { int a1[] = { , , , , , , , , , };
int a2[];
memmove(a2, a1, sizeof(a1)); // 输出a2的值
for (int i = ; i < sizeof(a2) / sizeof(int); i++) {
printf("a2[%d]=%x\n", i, a2[i]);
} } // memcpy函数的使用
void memcpyDemo() {
int a1[] = {,,,,,,,,,};
int a2[];
memcpy(a2,a1,sizeof(a1)); // 输出a2的值
for (int i = ; i < sizeof(a2) / sizeof(int); i++) {
printf("a2[%d]=%x\n",i,a2[i]);
} } // memset函数的使用
void memsetDemo() { char array[] = { };
array[] = ;
array[] = ;
array[] = ;
// 把这块内存的所有成员再次初始化为0
memset(array,,sizeof(array)); for (int i = ; i < sizeof(array) / sizeof(char); i++) {
printf("array[%d]=%d\n",i,array[i]);
}
}
五.字符数组与字符串指针
C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中:
#include <stdio.h>
int main(){
char str[] = "http://www.baidu.com";
int len = strlen(str), i;
//直接输出字符串
printf("%s\n", str);
//每次输出一个字符
for(i=; i<len; i++){
printf("%c", str[i]);
}
printf("\n");
return ;
}
运行结果:
http://www.baidu.com
http://www.baidu.com
或者是定义字符串指针,使用这种方式输出:
#include <stdio.h>
#include <string.h> int main()
{
char *str = "http://www.baidu.com";
int len = strlen(str), i;
for (i = ; i < len; i++) {
printf("%c", *str++);
}
printf("\n"); return ;
}
输出结果:
http://www.baidu.com
那这两种方式输出的区别在哪呢?区别在于:字符数组存储在全局数据区或者是栈区,而指针指向的字符串存储在常量区。全局数据区或栈区的字符串有读取和写入的权限,而常量区的字符串只有读取的权限,没有写入权限。内存权限不同导致的一个显著的结果就是:字符数组可以在定义后读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后,则不能对其进行修改。
例:修改字符串中第一个字符为a
#include <stdio.h>
#include <string.h> int main()
{ char *str = "http://www.baidu.com"; int len = strlen(str), i;
for (i = ; i < len; i++) {
printf("%c", *str++);
}
printf("\n"); *str = 'a'; return ;
}
运行结果:
http://www.baidu.com
Bus error: 10
出现了段错误。