指针
一、指针
int p ; // 这是一个普通的整型变量
int *p; // P是一个返回整型数据的指针
int p[3]; // P 是一个由整型数据组成的数组
int *p[3]; // 指针所指向的内容的类型是整型的 ,P 是一个由返回整型数据的指针所组成的数组
int (*p)[3]; // 数组里的元素是整型的。P 是一个指向由整型数据组成的数组的指针
int(*p)[3]; //指向含有3个int元素的数组的指针
int **p; // 首先从P 开始,先与*结合,说是P 是一个指针,然后再与*结合,说明指针所指向的元素是指针,然后再与int 结合,说明该指针所指向的元素是整型数据
int p(int); //从P 处起,先与()结合,说明P 是一个函数,然后进入()里分析,说明该函数有一个整型变量的参数,然后再与外面的int 结合,说明函数的返回值是一个整型数据
Int (*p)(int); //从P 处开始,先与指针结合,说明P 是一个指针,然后与()结合,说明指针指向的是一个函数,然后再与()里的int 结合,说明函数有一个int 型的参数,再与最外层的int 结合,说明函数的返回类型是整型,所以P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.
二、获取对象地址
int add(int a , int b)
{
return a + b;
}
int main(void)
{
int num = 97;
float score = 10.00F;
int arr[3] = {1,2,3};
int* p_num = #
int* p_arr1 = arr; //p_arr1意思是指向数组第一个元素的指针
float* p_score = &score;
int (*p_arr)[3] = &arr;
int (*fp_add)(int ,int ) = add; //p_add是指向函数add的函数指针
const char* p_msg = "Hello world";//p_msg是指向字符数组的指针
return 0;
}
三、解析地址对象
int age = 19;
int* p_age = &age;
*p_age = 20; //通过指针修改指向的内存数据
printf("age = %d\n",*p_age); //通过指针读取指向的内存数据
printf("age = %d\n",age);
四、指针值的状态
-
指向一个对象的地址
- 指向紧邻对象所占空间的下一个位置
- 空指针,意味着指针没有指向任何对象
- 无效指针(野指针),上述情况之外的其他值
空指针:在C语言中,我们让指针变量赋值为NULL表示一个空指针,而C语言中,NULL实质是 ((void*)0) , 在C++中,NULL实质是0。C++中也可以使用C11标准中的nullpte字面值赋值,意思是一样的。
任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。
无效指针:指针变量的值是NULL,或者未知的地址值,或者是当前应用程序不可访问的地址值,这样的指针就是无效指针,不能对他们做解指针操作,否则程序会出现运行时错误,导致程序意外终止。
未经初始化的指针就是个无效指针,所以在定义指针变量的时候一定要进行初始化。如果实在是不知道指针的指向,则使用nullptr或NULL进行赋值。
五、浅拷贝和深拷贝
如果2个程序单元(例如2个函数)是通过拷贝 他们所共享的数据的 指针来工作的,这就是浅拷贝,因为真正要访问的数据并没有被拷贝。如果被访问的数据被拷贝了,在每个单元中都有自己的一份,对目标数据的操作相互 不受影响,则叫做深拷贝。
六、面试经典题
1、
void GetMemory(char* p)
{
p = (char*)malloc(100);
}
void Test(void)
{
char* str = NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}
运行的结果是什么?
指针只是浅拷贝,申请的内存在临时对象p中,并没有传递到函数外面,然后又对str地址进行写操作,str初始地址为NULL,不能进行书写,所以系统会崩溃。
2、
int array[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
int *p = array;
p += 5;
int* q = NULL;
*q = *(p+5);
printf("%d %d",*p,*q);
运行结果是什么?
一看很开心是指针类型的加减法,下标从0开始,但是数字从1开始,所以应该是6 11。但是你忽略了q是一个NULL指针,不能进行书写,所以会崩溃。
3、
int arr[5] = {0,1,2,3,4};
const int *p1 = arr;
int* const p2 = arr;
*p1++;
*p2 = 5;
printf("%d,%d",*p1,*p2);
*p1++ = 6;
*p2++ = 7;
printf("%d %d \n", *p1, *p2);
指针指向数组,数组退化成指针,前两个指针操作是对的。但是后面*p1++ = 6; 不可以通过p1进行值的修改,*p2++ = 7;不能对p2进行修改。所以这道题是编译出错。
数组
一、初始化
int a[5] = {0,1,2,3,4};
int b[] = {0,1,2,3,4}; //b和a的效果是一样
int aa[2][5] = {0,1,2,3,4, 5,6,7,8,9}; //二维数组
二、指针和数组
int a[10];
int *const p = a;
std::cout << sizeof(a); // 40
std::cout << sizeof(p); // 64位机器上为8
2.1 数组名复制给指针
#include <stdio.h>
int main()
{
int a[] = {1,2,3};
int * p = a;
printf("a:\%p, p:%p, &a[0]:%p\n", a, p, &a[0]);
printf("*a:\%d, *p:%d, a[0]:%d, p[0]:%d\n", *a, *p, a[0], p[0]);
printf("*(a+1):\%d, *(p+1):%d, a[1]:%d, p[1]:%d\n", *(a+1), *(p+1), a[1], p[1]);
return 0;
}
输出:
a:0x5fcaf0, p:0x5fcaf0, &a[0]:0x5fcaf0
*a:1, *p:1, a[0]:1, p[0]:1
*(a+1):2, *(p+1):2, a[1]:2, p[1]:2
2.2 &a 与 &a[0]
&a表示数组地址,其结果是指向该数组的指针。
&a[0]表示数组首元素的地址,其结果是指向该数组首元素的指针。
它们意义不同,但是地址的值相同。因为数组是一连续内存,它的地址是所占内存单元的第一块存储单元地址。而第一块存储单元就是数组的首元素,所以两者相同。