1、指针与指针变量
启动一个程序,系统在内存上给该程序分配一块内存空间。如果是32位系统,内存大小为4G。
内存空间是由一个个字节构成,每个字节系统会分配一个编号,称为地址。
回顾C语言基础:
整型变量:int a = 10;
,其中,a为整型变量,存储整数。
指针变量:int *p
,其中,p为指针类型。指针变量可理解为存指针(地址)的变量。在32位系统中,因为地址编号格式为:0x 0000 0000,所以指针变量占4个字节。指针的实质就是地址
- 指针变量的定义和初始化
int main()
{
int a = 10;
//定义指针三步骤:1、* 与符号结合代表是一个变量; 2、要保存谁的地址,就将它的定义形式放在此处;3、用 *p 替换定义的变量
int *p;
//指针分析三步骤:
//1、与 * 结合代表这是一个指针变量
//2、p是变量,其类型是将p本身删除,剩下的类型就是指针变量的类型。在本例中为:int *
//3、指针变量p是用来保存什么类型的数据:将指针变量p和离它最近的 * 删除,剩下什么类型就保存什么类型数据的地址。在本例中为:int
p = &a; //赋值
system("pause");
return 0;
}
- 指针变量保存谁的地址就指向谁
*p = 100;//在使用时,*与p结合代表 取p指针所指向的那块空间的内容
//在使用时,对一个表达式取 * ,就相当于对表达式减少一级 * ;如果对表达式取 & ,就相当于加一级 *
在使用时,对一个表达式取 *,就相当于对表达式减少一级 * ;如果对表达式取 & ,就相当于加一级 *
- 指针变量的大小
无论是哪种类型指针,其大小只和编译器有关。
int main()
{
char *p1;
short *p2;
int *p3;
int **p4; //p4也是一个指针变量,它的类型为 int **
printf("%d\n", sizeof(p1));//大小:4
printf("%d\n", sizeof(p2));//大小:4
printf("%d\n", sizeof(p3));//大小:4
printf("%d\n", sizeof(p4));//大小:4
system("pause");
return 0;
}
- 指针的宽度和步长
指针宽度(步长) = sizeof(指针类型) 例如:sizeof(int *); sizeof(int **)等……
步长:指针加1跨过多少字节
char *p1 宽度:1字节
short *p2 宽度:2字节
int *p3 宽度:4字节
int **p3 宽度:4字节
int main()
{
int num = 0x01020304;
char *p1 = (char *)# //因为num为int 类型,所以对 num 取地址为 int * 类型,因此,需要强转为 short * 类型
short *p2 = (short *)#
int *p3 = #
//通过 * 取指针变量所指向空间的内容时,取的是内存的宽度,和指针变量本身的类型有关
printf("%x\n", *p1); //04
printf("%x\n", *p2); //0304
printf("%x\n", *p3); //01020304
//指针地址
printf("%p\n", p1);
printf("%p\n", p2);
printf("%p\n", p3);
//指针 + 1 的地址
printf("%p\n", p1 + 1);
printf("%p\n", p2 + 1);
printf("%p\n", p3 + 1);
system("pause");
return 0;
}
- 野指针
int main()//野指针
{
int *p;//此时 指针 p 的指向未定义,属于野指针,其指向是随机的,不可以操作野指针,运行会出错
*p = 2000;
printf("%d\n", *p);//err
int a = 10;
int *p = &a;//指针 p 指向变量 a
*p = 2000;
printf("%d\n", *p);//ok
system("pause");
return 0;
}
- 空指针
空指针作用:如果使用完指针将指针赋值为NULL,在使用时判断指针是否为NULL,就可以知道其是否被用
int main()//空指针
{
int a;
//将指针的值赋值为0,0x00000000 = NULL
int *p = NULL;
*p = 200; //err 因为P保存了0x00000000的地址,这个地址是不可以使用的
printf("%d\n",*p);
system("pause");
return 0;
}
- 万能指针
注意:定义 void * 类型, 其类型是4字节(32位系统下),打印的时候,系统并不知道 void类型 取多少字节,因此打印会报错,而定义不会报错
int main()//万能指针 void *
{
//void b;//err 不可以定义VOID类型的变量,因为编译器不知道给void类型的变量分配多少字节的空间
int a = 10;
short b = 200;
//可以定义void * 类型,因为其类型是4字节(32位)
void *p = (void *)&a; //万能指针可以保存所有类型的地址,但要注意地址库类型转换
void *q = (void *)&b;
//printf("%d\n", *p); //err 因为 P 是 void *,不知道取多少字节
printf("%d\n", *(int *)p); // *( (int *)地址) == 该地址中的内容
/*注意:定义 void * 类型, 其类型是4字节(32位系统下),打印的时候,系统并不知道 void*类型 取多少字节,因此打印会报错,而定义不会报错*/
system("pause");
return 0;
}
- const 关键字修饰
const 修饰变量 a ,意为不能通过a修改a内存中的值,但可以通过指针修改
int main()
{
//const 修饰变量 a ,意为不能通过a修改a内存中的值,但可以通过指针修改
const int a = 10;
int *p = &a;
*p = 100;
printf("%d\n", *p); // 100
printf("a = %d\n", a);// 100
system("pause");
return 0;
}
-
const int *p = &a; *不能通过 p 修改其指向内存中的内容,但可以通过变量a本身修改
-
int * const p = &a; p 保存的地址不可以修改
-
const int *const p = &a; *P本身的指向不能修改,也不能通过 p 修改p指向的内存中的内容
int main()
{
int a = 10;
//const 修饰 * ,意为:不能通过 *p 修改其指向内存中的内容,但可以通过变量a本身修改
const int *p = &a;
//*p = 100;//err 不能通过 *p 修改其指向内存中的内容,但可以通过变量a本身修改
a = 100;
printf("%d\n", *p); // 10
printf("a = %d\n", a);// 100
int b = 20;
//const 修饰 变量p ,意为:p 保存的地址不可以修改
int * const p = &a;
//p = &b; //err
*p = 123;
printf("%d\n", *p); // 123
const int *const p = &a;//P本身的指向不能修改,也不能通过 *p 修改p指向的内存中的内容
system("pause");
return 0;
}
- 多级指针
定义多级指针时,定义的指针类型只需要比想保存的的数据类型多一个 * 即可
“ int *p ” :代表这个p是一个指针变量,其中 p 为变量。
p的类型 :将p删除,剩下的类型
p保存谁的地址:将 * 和 p 一起删除,剩下的类型,就是其保存的类型。
int main()//多级指针
{
int a = 10;
int *p = &a;
//二级指针 q 保存 一级指针p 的地址,即 q -> p ->a
int **q = &p;
// **q == *(p) == a
// **q == *(p) == *(&a) == a
printf("%d\n", **q);//通过二级指针 q,取 变量a 中的值10
printf("%d\n", *p); //通过一级指针 p,取 变量a 中的值10
system("pause");
return 0;
}
- 通过指针操作数组
指针 + 1 :跨一个步长
要得到内存手中的内容,需要先拿到该内存的地址,再解引用取内容 : *(address)
int main()
{
//a 是数组名,保存的是首元素的地址 因此 a == &a[0]、 a+1 == &a[0+1] == &a[1]等等
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int *p = a;//指针 p 保存的是数组首元素的地址
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
//printf("%d ", a[i]);//通过数组下标访问数组元素
printf("%d ", *(p + i));//通过指针访问数组元素
}
int b[10] = { 0 };
for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
{
*(p + i) = i;//通过指针给数组赋值
}
for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
{
printf("%d ", *(p + i)); //0 1 2 3 4 5 6 7 8 9
}
system("pause");
return 0;
}
- 指针的运算
两指针类型一致,相减得到中间跨过多少个元素;
两指针相加无意义
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
int *p = a;
printf("%d\n", *(p + 3));//P+3 表示指针向后移动3个元素,再解引用,表示取当前地址中得内容,因此该语句输出为 4
//int *q = &a[9];该语句和下一行语句等价
int *q = (int *)(&a + 1) - 1; //&a+1 表示跨过整个数组,(&a+1) - 1表示让 指针q 指向数组a的最后一个元素的地址,两边类型需要匹配,所以要进行强转
printf("%d\n", q - p);//两指针相减,代表数组中有多少个元素
//printf("%d\n", p + q)); //err 在C语言中,了两指针相加无意义
system("pause");
return 0;
}
- [ ]不是数组的专属
[ ] <==> *()
int main()// [] 的使用
{
int a = 10;
int *p = &a;
*p = 100;
printf("%d\n", a);//此时,a = 100
// []不是数组的专属: [] == *()
int b[10] = { 1,2,3,4,5,6,7,8,9,10 };
int *p = b;
for (int i = 0; i < sizeof(b) / sizeof(b[0]); i++)
{
//下列打印语句都等价
printf("%d ", b[i]);
printf("%d ", *(p + i));
printf("%d ", p[i]); // p[i] == *(p + i)
printf("%d ", *(b + i));//由于 b 代表数组首元素地址,此时使用的方式同 指针p,因此 b[i] == *(b + i)
}
system("pause");
return 0;
}
- 指针数组
整形数组:是一个数组,数组中每个元素都是一个整型变量
指针数组:是一个数组,数组中每个元素都是一个指针
注意:一定要注意指针的指向,及其存的值是代表什么!!!
int main()//指针数组
{
int a = 10;
int b = 20;
int c = 30;
//指针数组,数组中每个元素保存一个地址
int *num[3] = { &a,&b,&c };
printf("%d\n", sizeof(num));// 指针数组的大小 = 类型 * 元素个数
//通过数组打印出每个元素指向内存的内容
for (int i = 0; i < sizeof(num) / sizeof(num[0]); i++)
{
printf("%d ", *num[i]);
}
//定义一个指针,保存指针数组num首元素的地址
//num[0] 为 int * 类型,要保存该类型的地址,需要用到二级指针保存
int **q = num; //二级指针保存 &num[0] == num == &(int *) == int **
printf("%d\n", **q); //**q == a的值,
//通过 二级指针q 打印出每个元素指向内存的内容
for (int i = 0; i < sizeof(num) / sizeof(num[0]); i++)
{
// q+1 等价于 &num[1];
// *(q+1) 得到num[1]中的内容,即变量b的地址( &b )
// *(*(q+1))得到 变量b 的内容
printf("%d ", *(*(q+i)));
}
system("pause");
return 0;
}
- 指针作为形参
值传递函数:不会修改实参的值 入参为变量,其实质是局部变量,子函数被调用完之后,局部变量会被释放,因此不会影响到实参的值
址传递函数:会修改实参的值 入参为地址,表示对改地址中的内容进行操作,即便返回主函数中,该内存中的值已经被改变,因此会改变实参的值
void swap1(int x, int y) //值传递
{
int temp = x;
x = y;
y = temp;
printf("交换后: x = %d, y = %d\n", x, y);
return;
}
void swap2(int *x, int *y) //址传递
{
int temp = *x;
*x = *y;
*y = temp;
printf("交换后: x = %d, y = %d\n", *x, *y);
return;
}
int main()
{
int a = 10;
int b = 20;
printf("值传递交换函数:\n");
printf("a = %d, b = %d\n", a, b);
swap1(a, b);
printf("址传递交换函数:\n");
printf("a = %d, b = %d\n", a, b);
swap2(&a, &b);
system("pause");
return 0;
}
- 数组作为函数的参数
数组作为函数的参数,在子函数形参列表中会退化为指针
Eg: void print_arr(int x[10]) ====> void print_arr(int *x)
//void print_arr(int x[10]) 数组作为函数的形参,会退化为指针 void print_arr(int x[10]) ====> void print_arr(int *x)
void print_arr(int *x, int n)
{
//int n = sizeof(x) / sizeof(x[0]);
for (int i = 0; i < n; i++)
{
printf("%d ", *(x + i));
}
printf("\n");
}
int main()
{
int a[10] = { 1,2,3,4,5,6,7,8,9,10 };
print_arr(a,10);//打印数组的内容
system("pause");
return 0;
}
- 指针作为函数返回值
int *getnum1()
{
srand(time(NULL));
//int num = rand();//num为子函数中的局部变量,该变量在子函数调用完后会被释放
num = rand();
return #//返回值为地址
}
int num = 0;//全局变量,整个工程都可以使用。程序启动时开辟空间,知道程序结束释放空间
int *getnum2()
{
srand(time(NULL));
//int num = rand();//num为子函数中的局部变量,该变量在子函数调用完后会被释放
num = rand();
return #//返回值为地址
}
int main()
{
int *p = getnum1(); //子函数中定义的变量num是局部变量,局部变量在子函数调用结束后会释放,因此会造成隐患,不建议这样使用
int *p = getnum2(); //在函数外定义的变量是全局变量,可以这样使用
printf("%d\n", *p);
system("pause");
return 0;
}