目录
一、指针简介
1.指针的定义
指针就是地址 。我们口头上说的指针其实指的是指针变量 。指针变量就是一个存放地址的变量。要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。
- 1.指针的类型:只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
cpp(1)int*ptr;//指针的类型是int* (2)char*ptr;//指针的类型是char* (3)int**ptr;//指针的类型是int** (4)int(*ptr)[3];//指针的类型是int(*)[3] (5)int*(*ptr)[4];//指针的类型是int*(*)[4]
- 2.指针所指向的类型:从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型
cpp(1)int*ptr; //指针所指向的类型是int (2)char*ptr; //指针所指向的的类型是char (3)int**ptr; //指针所指向的的类型是int* (4)int(*ptr)[3];//指针所指向的的类型是int()[3] (5)int*(*ptr)[4]; //指针所指向的的类型是int*()[4]
- 3.指针的值----或者叫指针所指向的内存区或地址
cpp1.指针的值是指针本身存储的数值,这个值将被 编译器当作一个地址,而不是一个一般的数值。 2.指针所指向的内存区就是从指针的值所代表的那个内存地址开始, 长度为sizeof(指针所指向的类型)的一片内存区。 3.我们说一个指针的值是XX,就相当于说该指针指向了 以XX 为首地址的一片内存区域; 4.我们说一个指针指向了某块内存区域,就相当于说 该指针的值是这块内存区域的首地址。
- 4.指针本身所占据的内存区
cpp只要用函数sizeof(指针的类型)测一下就知道了
2.指针类型说明
int p; //这是一个普通的整型变量
int *p;//首先从P 处开始,先与*结合,所以说明P 是一个指针,然后再与int 结合,说明指针所指向的内容的类型为int 型。所以P是一个返回整型数据的指针;
int p[3];//首先从P 处开始,先与[]结合,说明P 是一个数组,然后与int 结合,说明数组里的元素是整型的。所以P 是一个由整型数据组成的数组;
int *p[3];//首先从P 处开始,先与[]结合,因为其优先级比*高,所以P 是一个数组,然后再与*结合,说明数组里的元素是指针类型,然后再与int 结合,说明指针所指向的内容的类型是整型的。所以P 是一个由返回整型数据的指针所组成的数组;
int (*p)[3];//首先从P 处开始,先与*结合,说明P 是一个指针然后再与[]结合(与"()"这步可以忽略,只是为了改变优先级),说明指针所指向的内容是一个数组,然后再与int 结合,说明数组里的元素是整型的。所以P 是一个指向由整型数据组成的数组的指针;
int **p; //首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据。
int p(int);//从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据;
Int (*p)(int);//从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针;
3.运算符&和*
- 取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。
- 取指针运算符*(解引用:访问指针所指向的存储位置上的值 ):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。
- &a 的运算结果是一个指针,指针的类型是a 的类型加个*,指针所指向的类型是a 的类型,指针所指向的地址那就是a 的地址。
- *p 的运算结果就五花八门了。总之***p 的结果是p 所指向的东西**,这个东西有这些特点:它的类型是p 指向的类型,它所占用的地址是p所指向的地址。
- &和*都是右结合的,假设有变量 x = 10,则*&x 的含义是,先获取变量 x 的地址,再获取地址中的内容。因为&和*互为逆运算,所以 x = *&x。
cpp
int a=12; int b; int *p; int **ptr;
p=&a; //&a的结果是一个指针,类型是int*,
//指向的类型是int,指向的地址是a的地址。
*p=24;//*p的结果,在这里它的类型是int,
//它所占用的地址是p所指向的地址,显然*p就是变量a。
ptr=&p;//将ptr设置为指向p的地址。现在ptr存储了p的内存地址。
//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。
*ptr = &b;//解引用ptr(得到p)并将b的地址赋给p。
//现在p不再指向a,而是指向b。
**ptr = 34;//首先解引用ptr得到p,然后再次解引用p得到它所指向的变量(现在是 b)
//最后,将 34 赋值给 b。这等同于 b = 34;。
二、指针的运算
1.加减运算
- 指针变量的自增自减运算:指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。(指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节)
- 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
cpp
#include <stdio.h>
int main() {
// 假设 x, y, z 在内存中的地址分别是 4000, 4004, 4008,但这里我们只需要声明它们
int x, y, z;
// 假设 &x 的地址是 4000(在实际程序中这是不可知的)
// 定义一个指针 px,指向 x
int *px = &x; // 假设 px 的值是 4000(即 &x 的值)
// 利用指针变量 px 加减整数,分别输出 x、y、z
printf("x = %d", *px); // 因为 px 指向 x,所以 *px = x
// px + 1 表示向后移动一个 int 类型的大小(从 4000 到 4004)
printf("y = %d", *(px + 1)); // 假设这里实际上访问的是 y 的值
// px + 2 表示向后移动两个 int 类型的大小(从 4000 到 4008)
printf("z = %d", *(px + 2)); // 假设这里实际上访问的是 z 的值
return 0;
}
2.指针-指针
指针-指针的绝对值指的是两个指针之间元素的个数。
前提:两个指针必须指向同一空间
例如:&arr[9]-&arr[0]
3.关系运算
假设有指针变量 px、py。(指向不同数据类型之间的指针之间的关系运算是没有意义的)(指针与一般整数常量或变量之间的关系运算也是没有意义的)
- px > py 表示 px 指向的存储地址是否大于 py 指向的地址
- px == py 表示 px 和 py 是否指向同一个存储单元
- px == 0 和 px != 0 表示 px 是否为空指针
cpp//定义一个数组,数组中相邻元素地址间隔一个单元 int num[2] = {1, 3}; //将数组中第一个元素地址和第二个元素的地址赋值给 px、py int *px = &num[0], *py = &num[1]; int *pz = &num[0]; int *pn; //则 py > px if(py > px){ printf("py 指向的存储地址大于 px 所指向的存储地址"); } //pz 和 px 都指向 num[0] if(pz == px){ printf("px 和 pz 指向同一个地址"); } //pn 没有初始化 if(pn == NULL || pn == 0){ printf("pn 是一个空指针"); }
三、指针与数组
1.指针与一维数组
1.区分以下几种数组名情况:
- sizeof(arr):表示的是整个数组的大小。
- &arr: 表示整个数组,取出的是整个数组的大小。
- 其他情况数组名都表示数组首元素地址。
2.易错点:
- 数组名a不等价于指针变量p,指针变量p可以进行 p++和&操作,而这些操作对于数组名a是非法的(例:a=p和a++都是错的,因为数组名在编译时是确定的,在程序运行期间算一个常量)
- *++p:先对 指针p 进行递增操作(地址++),然后对递增后的 p 进行解引用操作,即访问 p 所指向地址上的值。
- (*p)++:最后是*p的结果加1。
- 在 p+整数的操作要考虑边界的问题,如一个数组长度为 2,p+3 的意义对于数组操作来说没有意义。
cpp
//定义一个整形数组,并初始化
int nums[5] = {4, 5, 3, 2, 7};
//定义一个指针变量 p,将数组 nums 的首地址赋值给 p,也可以用p = &nums[0]赋值
int *p = nums, i; //i 作为循环变量
//p 指向数组第一个元素(数组首地址),我们可以直接用间接寻址符,获取第一个元素的内容
printf("nums[0] = %d\n", *p); //输出结果为 nums[0] = 4
//我们可以通过"p + 整数"来移动指针,要先移动地址,所以 p + 1 要扩起来
printf("nums[1] = %d\n", *(p + 1)); //输出结果为 nums[1] = 5
//由上面推导出*(p + i) = nums[i],所以我们可以通过 for 循环变量元素
for(i = 0; i < 5; i++){
printf("nums[%d] = %d", i, *(p + i));
}
2.字符指针与字符数组
1.字符指针可以指向一个字符串,用字符串常量对字符指针进行初始化。
cppchar *str = "This is a string.";
- 字符指针指向的是一个字符串常量的首地址,即指向字符串的首地址。
- str是一个变量可以改变str使它指向不同的字符串。
- 由于 str 指向的是只读内存区域,所以不能修改字符串内容。
- 使用
sizeof(str)
时,将返回指针的大小2.这里要注意字符指针与字符数组之间的区别。
cppchar string[ ]="This is a string.";
- string是字符数组,它存放了一个字符串。
- string是一个数组,可以改变数组中保存的内容。
- 使用
sizeof(str)
时,将返回字符数组的大小2.实例:
cpp1.char *str, *str1="This is another string."; str++; /* 指针str加1 */ str = "This is a NEW string."; /* 使指针指向新的字符串常量 */ str = str1; /* 改变指针str的指向 */ strcat(str, "This is a NEW string.") /* 不能在str的后面进行串连接 */ strcpy(str, string) /* 不能向str进行串复制 */ char string[100]="This is a string."; strcpy( string, "This is a NEW string.") /* 改变字符串的的内容 */ strcat( string, str) /* 进行串连接操作 */ string++; /* 不能对数组名进行++运算 */ string = "This is a NEW string."; /* 不能对数组名(常量)赋值*/ string = str1; /* 对数组名不能进行赋值 */ 2.char arr[29] ; arr[]= "This is a string."//(这是错误的) 数组在声明后不能直接使用[]进行整体赋值。(c语法不支持) 你只能在声明的同时进行初始化或者之后通过逐个元素赋值 或使用特定的函数(如strcpy)来修改数组的内容。 3. 以字符串形式出现的,编译器都会为该字符串自动添加一个'0/'作为结束符, 如在代码中写:"abc",那么编译器帮你存储的是"abc/0" 4. 对于char str[] = "abcdef";就有sizeof(str) == 7,因为str的类型是char[7], 也有sizeof("abcdef") == 7,因为"abcdef"的类型是const char[7]。 对于char *ptr = "abcdef";就有sizeof(ptr) == 4,因为ptr的类型是char*。 对于char str2[10] = "abcdef";就有sizeof(str2) == 10,因为str2的类型是char[10]。 对于void func(char sa[100],int ia[20],char *p); 就有sizeof(sa) == sizeof(ia) == sizeof(p) == 4, 因为sa的类型是char*,ia的类型是int*,p的类型是char*。
3.指针与二维数组
1.指针与二维数组
cppa;//代表数组首行地址,一般用a[0][0]的地址表示 &a;//代表整个数组的地址,一般用a[0][0]地址表示 a[i];//代表了第i行起始元素的地址 &a[i];//代表了第i行的地址,一般用a[i][0]的地址表示 a[i]+j;//代表了第i行第j个元素地址,a[i]就是j==0的情况 a[i][j];//代表了第i行第j个元素 &a[i][j];//代表了第i行第j个元素的地址 *a;//代表数组a首元素地址也就是a[0]或者&a[0][0] *(a+i);//代表了第i行首元素的地址,*a是i=0的情况 *(a+i)+j;//代表了第i行j个元素的地址 **a;//代表a的首元素的值也就是a[0][0] *(*(a+i)+j);//代表了第i行第j个元素
2.等价关系
cppa+i == &a[i] //a+i和&a[i]意义相同,都表示指向第i行的指针(行地址) a[i] == &a[i][0] == *(a+i) //a[i],&a[i][0],*(a+i)意思相同,都表示该行起始元素地址。 *(*(a+i)+j) == a[i][j]== = *(a[i]+j) //都表示第i行第j列元素
4.数组指针、指针数组
1.数组指针 :数组的指针,是一个指针,指向数组的指针。(优先级顺序:()>[]>*)
- 数组指针是一个指针变量,占有内存中一个指针的存储空间;
- (*p)[n]:根据优先级,先看括号内,则p是一个指针,这个指针指向一个一维数组,数组长度为n,这是"数组的指针",即数组指针;
- int (*p2)[5];p2 是一个指针,它指向一个包含 5 个 int 类型数据的数组
cpp#include "stdio.h" int main() { int a[5] = { 1, 2, 3, 4, 5 }; int (*p)[5]; p = &a;//把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身 //在C中,在几乎所有使用数组的表达式中,数组名的值是个指针常量, //也就是数组第一个元素的地址,它的类型取决于数组元素的类型。 printf("%p\n", a); //输出数组名,则输出数组首元素地址 printf("%p\n", p); //根据上面,p为数组a的地址,输出数组a的地址 printf("%p\n", *p); //*p表示数组a本身,一般用数组的首元素地址来标识一个数组 printf("%p\n", &a[0]); //a[0]的地址 printf("%p\n", &a[1]); //a[1]的地址 printf("%p\n", p[0]); //数组首元素的地址 printf("%d\n", **p); //*p为数组a本身,即为数组a首元素地址,则*(*p)为值,**p表示首元素的值1 printf("%d\n", *p[0]); //p存储的是一个数组的地址,而不是单个整数的地址。 //根据优先级,p[0] 表示首元素地址,则*p[0]表示首元素本身,即首元素的值1 printf("%d\n", *p[1]); //p[1] 实际上是在尝试访问 p 后面一个指向数组的指针 //(这通常是非法的,因为 p 后面可能并没有另一个这样的指针) return 0; }
2.指针数组: 指针的数组,是一个数组,装着指针的数组。
- 指针数组是多个指针变量,以数组的形式存储在内存中,占有多个指针的存储空间。
- *p[n]:根据优先级,先看[],则p是一个数组,再结合*,这个数组的元素是指针类型,共n个元素,这是"指针的数组",即指针数组。
- int *p1[5]:该数组包含 5 个指向 int 类型数据的指针。
cpp#include "stdio.h" int main() { int a = 1; int b = 2; int *p[2]; p[0] = &a; p[1] = &b; printf("%p\n", p[0]); //a的地址 printf("%p\n", &a); //a的地址 printf("%p\n", p[1]); //b的地址 printf("%p\n", &b); //b的地址 printf("%d\n", *p[0]); //p[0]表示a的地址,则*p[0]表示a的值 printf("%d\n", *p[1]); //p[1]表示b的地址,则*p[1]表示b的值 return 0; }
四、指针与函数
1.指针函数、函数指针
**1.指针函数:**指针的函数,本质是一个函数,此函数返回某一类型的指针。
- 格式:数据类型 *函数名(形参列表)
cppint *f(x,y);
- 首先它是一个函数,只不过这个函数的返回值是一个地址值。(return语句中返回的可以是变量的地址,数组的首地址或指针变量等)
- 辨别方式:看函数名前面的指针*号有没有被括号( )包含,如果被包含就是函数指针,反之则是指针函数。
- 函数不能嵌套定义,也不能将函数作为参数传递。
**2.函数指针:**函数的指针,本质是一个指针,指向函数的指针变量,其包含了函数的地址,通过它来调用函数。
- 格式:数据类型 (*函数指针名)();
cppint (*func)(int,int); 声明一个指向同样参数、返回值的函数指针类型
- 函数地址:函数和变量一样,在存储空间中有地址,函数存储首地址又称为函数的执行入口。
- 指向函数的指针:可以将指针变量指向函数首地址,称这种指针为指向函数的指针,简称为函数指针。
- 函数指针指向区域是内存的程序代码存储区,函数指针的*运算是调用执行指向的函数。
2.函数的形参和实参
**1.实际参数(实参):**真实传给函数的参数,叫实参。
- 实参可以是:常量、变量、表达式、函数等。无论实参是何种类型的量,在进行函数调用时,它们必须有确定的值,以便把这些值传送给形参。
- 形参不影响实参。
**2.形式参数(形参):**形式参数是指函数名后括号中的变量。
- 因为形式参数只有在函数被调用的过程中才实例化(分配内存单元)所以叫形式参数。
- 形式参数当函数调用完成之后就自动销毁了,因此形式参数只在函数中有效。
cpp#include<stdio.h> #include<stdlib.h> void Swap1(int x, int y) { int tmp = 0; tmp = x; x = y; y = tmp; }//尽管x和y在函数内部被交换了,但这种交换对于函数外部的num1和num2没有任何影响。 //因为 x 和 y 是通过值传递的,也就是说它们是num1和num2的副本。 //在函数内部对 x 和 y 的操作不会影响到 num1 和 num2。 void Swap2(int *px, int *py) { int tmp = 0; tmp = *px; *px = *py; *py = tmp; }//由于px和py是指针,它们分别指向num1和num2的内存地址。 //因此在函数内部对*px和*py的操作实际上是直接修改了num1和num2的值。 //所以当Swap2函数被调用后,num1和num2的值确实被交换了。 int main() { int num1 = 1; int num2 = 2; Swap1(num1, num2); printf("%d %d\n", num1, num2); Swap2(&num1, &num2); printf("%d %d\n", num1, num2); system("pause"); return 0; }
感谢来自于大佬们 的资料分享,感谢佬: