有关指针的那些事儿《一》
指针是什么?
指针在C语言中的含义就是地址,而存放地址的变量叫做指针(变量)。
指针在32位操作系统下,都是 四字节,64位为8字节。
不太理解?举个例子吧。
什么是指针,什么是指针变量,解引用是访问什么?
可以看到a的地址和p的内容 是相同的。
指针怎么用?
有地址的数据都可以用指针来指向它,来完成一些操作。
-
指针类型
根据数据类型,指针也分许多类型。(字符指针、整型指针、数组指针、函数指针、结构体指针、等等) 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。 比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。
-
野指针
野指针概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的) 野指针成因 1. 指针未初始化 2. 2. 指针越界访问。 何规避野指针: 3. 指针初始化 4. 小心指针越界 5. . 指针指向空间释放即使置NULL 6. 指针使用之前检查有效
-
指针运算
指针+- 整数: 指针-指针: 指针的关系运算:
-
二级指针:
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
字符指针
字符指针的要点!
1.字符串的名字代表字符串首元素的地址。
2.字符指针不可以修改字符内容。因为这里字符的地址在系统内存字符常量区,只可读,不可修改。
注意注释里的内容!
` char* arr = "hello.c" ;//*arr是字符指针
printf("%d\n", sizeof(arr));//sizeof(arr)==sizeof(type of arr)这里字符串名字arr的数据类型为char*,即字符型指针。32位操作系统下,指针都是4字节。64位:8字节。
printf("%d\n", sizeof(*arr));//sizeof(*arr)==sizeof(type of *arr)这里*arr是指针的解引用,“arr”字符串名代表首元素的地址,所以sizeof(*arr)==sizeof(h),
//“h”是一个字符,但它的数据类型是前面开辟空间时所决定的,这里是char型,所以结果为1。
/**arr = *(arr+1);*/ //可以看到指针字符串是不能被修改的,因为指针字符串中的字符是在系统内存中的字符常量区,只可读取,不可被修改。
//我们使用指针字符串的时候,只是使用了字符存在系统内存中的地址,通过地址来访问和排序的。
printf("%c\n", *arr + 1);//由于“*”优先级高于“+”。所以先解引用再加1。解引用后是“h”,“h+1”是什么呢?是系统字符常量区中“h”的下一个字符!
printf("%c\n", *(arr + 1));//这里解引用的是“arr+1”。arr代表首元素地址,+1是第二个元素的地址!再解引用就是第二个元素‘e’。
//所以我们在使用指针的时候要多注意优先级的问题,为了确保无误可以加()。
//---------------------------------------------------------------------------
`
字符指针不能修改字符的运行结果图。
至于为什么也在注释里写了。
数组名相关
这里是存放在数组里的字符串相关。
要点有两个:
1.sizeof(数组名)和 &数组名 中的数组名代表整个数组,其他情况都是指首元素地址。
2.为什么数组字符串可以修改呢?因为数组是在栈帧上开辟空间,并且对字符常量区进行拷贝放入数组开辟的内存中。所以可以被修改。
//---------------------------------------------------------------------------
//数组字符串的数据类型大小,以及可否被修改验证。
char arr2[] = { "hello world!" };
printf("%d\n", sizeof(arr2));//sizeof(数组名)和 &数组名 中的数组名代表整个数组,其他情况都是指首元素地址。所以“arr2”的大小,是char类型所占字节大小×数组元素个数。字符串最后系统给一个\0
printf("%d\n", sizeof(*arr2));//第一个元素的数据类型所占字节数。
arr2[0] = arr2[11];//用数组下标修改数组字符串中的字符。
char* p = arr2;//这是一个指向数组内部首元素的指针,因为数组名在这里代表首元素地址。
*(p + 2) = arr2[11];//用指针修改数组字符串中的字符。
printf("%s\n", arr2);//为什么数组字符串可以修改呢?因为数组是在栈帧上开辟空间,并且对字符常量区进行拷贝放入数组开辟的内存中。所以可以被修改。
//---------------------------------------------------------------------------
一道面试题目:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
char *str3 = "hello bit.";
char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
}
答案:
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。
指针数组和数组指针
指针数组:
指针数组是指针还是数组?
答案:是数组。是存放指针的数组。
int* arr3[5];//是什么?
arr3是一个数组,有五个元素,每个元素是一个整形指针。
数组指针:
数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉: 整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
解释:因为操作符” [ “的优先级大于” * “所以p1先和” [ “结合,
说明p1是一个数组,是一个存放int 类型的数组。即int型指针数组
p2先和结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。
所以p2是一个指针,指向一个数组,叫数组指针。指向一个int[10]的指针。
//这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合
&数组名VS数组名
对于下面的数组
int ar[10];arr
和 &arr
分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。
那&arr
到底是啥?
我们看一段代码:
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
可见数组名和&数组名打印的地址是一样的。
难道两个是一样的吗?
我们再看一段代码:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("arr = %p\n", arr);
printf("&arr= %p\n", &arr);
printf("arr+1 = %p\n", arr+1);
printf("&arr+1= %p\n", &arr+1);
return 0;
}
根据上面的代码我们发现,其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。
&arr+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40.
而给arr+1,arr代表首元素的地址,+1只是指向下一个数组内的元素。
数组传参
不论是数组传参还是指针传参,其实传的都是地址。
我们知道,指针变量本身存放的就是地址,那么数组传参传的是什么呢?
记住!!!
数组传参时,发生降维,传的是降维之后第一个元素的地址。
这里的元素指的是:
一维数组传参:
#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
1、2、3、4、5均可以。
arr是一个int数组,降维后是一个int*。
arr2是一个存放int类型的数组[20],降维后是一个int**
1、2、原因是数组传参时降维,传的都是首元素的地址,类型为int。所以编译器不关心[ ]里面填写的数字,写多少都可以。所以1、2、正确。
3、中, arr是首元素的地址,类型为int*,自然可以。
4、中降维后传的是指针,只要数据类型匹配,int**=int就可以。
5、可以,是因为传参数据类型不匹配,传的是int,而test2接受的是int**。也正确!
结论:传参时候,数据类型匹配即可!名字不重要!
二维数组传参
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
根据总结,1、3可以。2不行。
void test(int *arr)//ok?
{}
void test(int* arr[5])//ok?
{}
void test(int (*arr)[5])//ok?
{}
void test(int **arr)//ok?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
根据一维数组传参得出来的结论,降维后传的是int (* )arr[5].//数组指针
1中接收的类型为int* 。错误
2中接收的类型为int * arr[5]//指针数组。错误
3中接收的类型为int(*)[5].正确
4中接收的类型为int * arr[5],错误。这里的 **arr看作 *(*arr)。arr代表首元素地址。解引用后(*arr)=arr[5]。所以接收类型为int * [5]
指针传参
一级指针传参
#include <stdio.h>
void print(int *p, int sz)
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
思考:
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
比如:
void test1(int *p)
{}
//test1函数能接收什么参数?
void test2(char* p)
{}
//test2函数能接收什么参数?
test1可以接收
1:一级in型指针*p
2:int型一维数组(数组降维后就是一级指针)
test2可以接收
1:一级char 型指针
2:char型一维数组
3:char字符串名字。
二级指针传参
#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);
return 0;
}
思考:
当函数的参数为二级指针的时候,可以接收什么参数?
以下三种!
void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}
- 字符指针
- 数组指针
- 指针数组
- 数组传参和指针传参
- 函数指针
- 函数指针数组
- 指向函数指针数组的指针
- 回调函数
这篇文章主要讲述了1、2、3、4。
剩下的5、6、7、8。下篇文章见!