int i | 定义整形变量 |
int *p | 定义一个指向int的指针变量 |
int a[10] | 定义一个int数组 |
int *p[10] | 定义一个指针数组,其中每一个数组元素指向一个int型变量的地址 |
int (*p)[10] | 定义一个数组指针,指向int[10]类型的指针变量 |
int func() | 定义一个函数,返回值为int类型 |
int *func() | 定义一个函数,返回值为int* |
int (*p)() | 定义一个指向函数的指针,函数的原型为无参,返回值为int |
int **p | 定义一个指向int的指针的指针,二级指针 |
指针是什么
指针也是一个变量,一种特殊的变量,这个变量存储的内容是一个地址,该地址指向一块内存空间。
指针变量的定义
指针可以指向一个变量的指针变量,这个指针变量的值是一个地址,不是常数,也不是一个其他类型的变量。
int a = 10;
int *p = &a;
int *p; //表示定义了一个指针变量
*p //代表的是指针所指的实际数据
p //是变量名 类型为int *
int a = 0;
printf("%d\n",&a);
//&表示取变量地址
int *P = &a;
printf("%X\n",p);
//*p定义了一个int类型的指针变量,指向一个int的地址
//p = &a 吧a的地址赋值给了p这个变量
//地址虽然是一个整数,但地址是一个特殊的整数,是不能直接通过整数来操作的
*p = 10;
printf("a = %d\n",a);
//通过指针间接的修改指针指向变量的值
指针运算
指针运算不是简单的加减,而是指针指向的数据类型在内存中占用字节数作为倍数的运算
char *p;
p++;//移动了sizeof(char)这么多的字节数;
int *p;
p++; //移动了sizeof(int)这么多的字节数;
二级指针(指向指针的指针)
指针是一个变量,既然是变量就存在内存地址,所以可以定义一个指向指针的指针。
int i = 10;
int *p = &i;
int **p1 = &p;
一次类推可以定义3级指针甚至多级指针。经常使用一级指针和二级指针。
注意:
1、指针变量只能存放地址,不能将一个常数直接赋值给一个指针(int *p = 100;//error)
2、指针变量必须初试化,不然就是野指针,程序中避免野指针的出现。
赋值:int *p = &a;
求值:int i = *p;
取指针地址:int **p1 = &p;
将一个整数加(减)给指针:p+3、p-3;
自增自减:p++、p--;
比较p1 == p2,通常用来比较两个指针是否指向同一个内存地址
空指针和野指针
void *p;
//无类型指针,只是一个指针变量,不指向任何具体的数据类型
p = NULL;
printf("%d\n",sizeof(p));
//指针赋值为NULL,称为空指针
int a = 10;
int *p;
//p就是一个野指针,程序中要避免野指针的存在,野指针是导致程序崩溃的主要原因
指针的兼容性
在一般类型变量中,存在自动类型转化,而在指针类型变量中,指针类型不兼容自动数据类型转化
float pi = 3.14;
int i = pi;
//自动数据类型转化,将浮点数的小数部分舍弃
int *p = π
//严重错误,因为指针类型不兼容
常量指针与指针常量
我们从名称着手就变得容易了
例如常量指针,常量在前,指针在后,可写作const int *p;
例如指针常量,指针在前,常量在后,可写作int *const p;
常量指针(值不可变,地址可变)
int a = 10;
int b = 30;
const int *p = &a;
p = &b;
//p这个指针只能指向一个常量
//在这里*p是一个只读的值,不能修改
//注:*p = 20; 不能通过 *p 来修改一个const的指针
//可以修改指向的地址,不能修改值
指针常量(值可变,地址不可变)
int a = 10;
int b = 30;
int *const p = &a;
*p = b;
//可以通过常量指针修改或者读取一个变量的值
//注意 p = &b; 是错误的,常量指针一旦被定义了,那么就不能修改其指向的变量
//可以修改值,不能修改指向的地址
使用const成员函数
应尽可能地使用const成员函数,很多人对使用const成员函数的目的不了解,其实它只是一种方法,让函数对调用者承诺“不会修改对象”,而编译器确保这个承诺。这是一个保证代码没有副作用的好方法。不会在任何你不知道的情况下改变对象。