1 什么是指针
- 指针就是地址
- 指针变量是用来存储地址的
2 如何定义指针
int num = 100;
int * p = #
- 定义了一个存储有整型变量num地址的指针变量p,通常称p指向num
- p可称为整形指针
- p这个指针变量的类型为int *
3 指针变量的基本使用
- &:取地址符
- *:寻址符,通过一个指针找到它所对应的内存空间
#include<stdio.h>
int main(void){
int num = 100;
int * p = #
printf("&num = %p\n", &num);// 打印num的地址
printf("p = %p\n", p); // 打印p的值 p中存储的就是num的地址
printf("num = %d\n", num); // 打印num的值
printf("*p = %d\n", *p); // 打印*p的值 等同于 num的值
printf("&p = %p\n", &p); // 打印指针变量p的地址
printf("%d\n", sizeof(p)); // 求的是指针变量p所占用的内存空间
printf("%d\n", sizeof(*p)); // 求的事num所占用的内存空间
printf("%d\n", sizeof(num));// 求的事num所占用的内存空间
return 0;
}
运行结果:
4 指针的存储
- 32位机,指针占用4个字节内存空间
- 64位机,指针占用8个字节内存空间
- 通常开发环境中,无论32位机还是64位机,指针都占4个字节
5 指针的初始化
- 没有进行初始化的指针称为野指针,指向哪是不确定的
- 对于指针初始化可以赋值为某个变量的地址
- 如果指针在定义时指向哪不确定,需要在程序运行过程中进行赋值,那么它的初始值可赋值为NULL,称这种指针为空指针
6 指针所支持的运算
- *:寻址符
- &:取地址符
- =:赋值运算符,可将一个地址赋值给指针变量
- ==、!=、>、<:关系运算符
- ==、!=:判断两个指针指向的是否是同一块内存
- >、<:判断指针存储地址的大小关系
- +:指针+整数
- 指针每+1,实际上是往后移动一个所指向变量类型单位的长度
- -:指针-整数
- 指针每-1,实际上是往前移动一个所指向变量类型单位的长度
- -:指针-指针
- 求的是两个指针地址之间,单位元素的个数
- 不存在指针+指针的运算
#include<stdio.h>
int main(void){
int arr[4] = {1,2,3,4};
int *p1 = &arr[0];
int *p2 = &arr[1];
int *p3 = &arr[2];
int *p4 = &arr[3];
printf("p1 = %p\n", p1);
printf("p2 = %p\n", p2);
printf("p3 = %p\n", p3);
printf("p4 = %p\n", p4);
printf("p4-p1 = %d\n", p4-p1);
return 0;
}
运行结果:
7 多重指针
- 若一个指针存储的内容是另一个指针的地址,则称这个指针为多重指针
- 二级指针:指向一级指针的指针
- 三级指针:指向二级指针的指针
- 注意:
- 允许连续寻址
- 不允许连续取地址
#include<stdio.h>
int main(void){
int num = 10;
int * p1 = # // &num -> 指针(地址),带类型 int *
int ** p2 = &p1; // p2 二级指针,指向一级指针的指针
printf("num = %d\n", num); // num这块内存,10
printf("*p1 = %d\n", *p1); // num这块内存,10
printf("**p2 = %d\n", **p2); // num这块内存,10
printf("&num = %p\n", &num); // p1这块内存,num的地址
printf("p1 = %p\n", p1); // p1这块内存,num的地址
printf("*p2 = %p\n", *p2); // p1这块内存,num的地址
printf("&p1 = %p\n", &p1); // p1的地址
printf("p2 = %p\n", p2); // p1的地址
return 0;
}
8 指针与一维数组
-
当一个指针指向数组某元素时,可以通过这个指针加减来访问数组中的每一个元素。
-
下标访问元素本质上就是指针加减寻址
#include <stdio.h>
int main(void)
{
int i;
int arr[5] = {11, 22, 33, 44, 55};
int * p = &arr[0];
for (i=0; i<5; i++) {
// 下标访问元素 和 指针寻址访问元素 本质上是等价的
// arr[i] => *(arr+i) 下标本质上就是指针寻址
printf("%d[arr] = %d\n", i, i[arr]);
printf("arr[%d] = %d\n", i, arr[i]);
printf("*(arr+%d) = %d\n", i, *(arr+i));
printf("*(p+%d) = %d\n", i, *(p+i));
printf("p[%d] = %d\n", i, p[i]);
}
return 0;
}
- arr在数值层面:表示arr数组首元素的地址(指针)
- arr在变量层面:表示一个长度为5、元素类型为int的数组
- 数组名,可以理解成它是一个特殊的指针,类似于指针而又和指针有区别
#include <stdio.h>
int main(void)
{
int i;
int arr[5] = {11, 22, 33, 44, 55};
int * p = arr;
// 相同点
for (i=0; i<5; i++) {
printf("%d\n", arr[i]); // *(arr+i)
printf("%d\n", p[i]); // *(p+i)
}
// 不同点
printf("%p\n", arr); // 数组首元素(arr[0])的地址
printf("%p\n", p); // 数组首元素(arr[0])的地址
printf("%p\n", &arr); // 整个数组(int arr[5])的地址
printf("%p\n", &p); // 指针变量p的地址
printf("arr + 1 = %p\n", arr + 1); // 移动一个int单位
printf("&arr + 1 = %p\n", &arr + 1); // 移动一个数组(int [5])单位
printf("sizeof(arr) = %d\n", sizeof(arr)); // 20 求的是整个数组(int arr[5])的大小
printf("sizeof(p) = %d\n", sizeof(p)); // 4 求的是指针变量p的内存大小
return 0;
}
- 数组指针
- 本质上是一个指针,指向数组的指针
- 指针数组
- 本质上是一个数组,存储指针的数组
#include<stdio.h>
int main(void){
int arr[5] = {11, 22, 33, 44, 55};
//p就是数组指针
int (*p)[5] = &arr;
// brr 就是一个指针数组,每一个元素都是一个指针
int * brr[5];
printf("%p\n", p);//p为数组arr的地址,输出数组arr的地址
printf("%p\n", &arr);//输出数组arr的地址
printf("%p\n", arr);//数组名,一般用一个数组的首元素地址来标识一个数组,即输出数组首元素的地址
printf("%p\n", *p);//数组首元素的地址
printf("%p\n", p[0]);//数组首元素的地址
printf("%p\n", &arr[0]);//数组首元素的地址
return 0;
}
运行结果:
总结:p[1] => *(p+1) => arr[1] = >*(arr+1)
p[0] = >*p => arr[0] = >*arr
p+1 = >&arr[1] =>&(*(arr+1))
p = >&arr[0] = >&(*(arr+0)) => &(*arr) => arr
9 指针与字符串
- 字符串
- 存储有字符串的字符数组
- 字符串字面值
- 本质上是一个指针,指针指向内存的只读区。
- 字符串字面值,实际上会在只读去存储一个字符数组,数组中的元素就是字符串字面值里面的字符+\0,然后字符串字面值就是指向这个数组首元素的地址。
#include <stdio.h>
int main(void)
{
char arr[] = "ABCD"; // arr是一个字符数组,数组里面保存着 ABCD\0
// 字符串的数据保存在自己的内存中
char * p = "ABCD"; // p是一个字符指针,指向只读区保存 ABCD\0 的位置
// 字符串的数据保存在只读区
printf("%s\n", arr); // 传入的是arr数组首元素的地址
printf("%s\n", p); // 传入的是指针p保存的地址,只读区存放ABCD\0数组首元素地址
printf("%s\n", "ABCD"); // 只读区存放ABCD\0数组首元素地址
arr[0] = 'B'; // 修改数组arr首元素的值,允许!栈区
// *p = 'B'; // 尝试往只读区写入数据,不允许!
// arr = "1234"; // arr不允许指向其它位置
p = "1234"; // p指向只读区存放 1234\0的位置
return 0;
}
- 字符串在输出时,需要传入一个指针,然后从这个地址开始一次取字符,如果当前字符不是'\0'则输出,并且指针+1,继续上述操作,直到遇到'\0'未知。
#include <stdio.h>
int main(void)
{
char arr[] = "ABCD1234";
char * p = "ABCD1234";
// 字符串输出函数需要是一个指向存储有字符串内容
// 的字符数组首元素的地址
printf("%s\n", arr+4);
printf("%s\n", p+4);
printf("%s\n", "ABCD1234"+4);
printf("%s\n", arr);
printf("%s\n", p);
printf("%s\n", "ABCD1234");
return 0;
}
9.1 字符串函数
1、strcmp(str1, str2):字符串比较
str1 = str2 返回0
str1 < str2 返回负数
str1 > str2 返回正数
2、strlen(str) :求字符串长度
3、strcat(str1, str2):字符串拼接
将字符串str2拼接到str1的后面
4、strcpy(str1, str2):字符串复制
将字符串str2复制给str1
9.2 字符指针数组
// 字符指针数组
// 数组里面每一个元素都是一个字符指针
char * arr1[5] = {"111", "222", "333", "444", "555"};
char * temp;
int i;
for (i=0; i<5; i++) {
puts(arr1[i]);
}
10 集中特殊的指针
-
const指针
#include <stdio.h>
int main(void)
{
int num = 10;
// 常量指针,指向常量的指针
const int * p1 = # // 表示 *p1 不允许修改
int const * p2 = # // 表示 *p2 不允许修改
// 指针常量,指针本身是常量
int * const p3 = # // 表示 p3 不允许被修改
// 常量常指针
int const * const p4 = # // 表示p4 不允许被修改,*p4也不允许被修改
return 0;
}
11 指针与二维数组
int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};
- a是数组名,a数组包含3行元素,即a[0],a[1],a[2]。每个行元素都可以看成含有4个元素的一维数组。则a[0],a[1],a[2]分别是这三个一维数组的数组名。
- a[0],a[1],a[2]分别是这三个一维数组的数组名,而一维数组名表示的是数组第一个元素的地址,所以a[0]表示元素a[0][0]的地址。
a[0] => &a[0][0] a[0]+1 => &a[0][1]
a[1] => &a[1][0] a[1]+1 => &a[1][1]
a[2] => &a[2][0] a[2]+1 => &a[2][1]
a为数组名,即为一维数组首元素的地址,而此一维数组的首元素是一维数组a[0]
a => &a[0] => &(&a[0][0]) 二维数组名a是地址的地址
a+1 => &a[1]
a+2 => &a[2]
int a[3][4] = {{1, 3, 5, 7}, {9, 11, 13, 15}, {17, 19, 21, 23}};
//两种定义方式
int **p = a;
int *p[4] = a;
- p => &a[0] *p => a[0] => &a[0][0] *p+1 => &a[0][1]
- p+1 => &a[1] *(p+1) => a[1] => &a[1][0] *(p+1)+1 => &a[1][1]
- p+2 => &a[2] *(p+2) => a[2] => &a[2][0] *(p+2)+1 => &a[2][1]
- *(p+i)+j => &a[i][j]