C语言—指针详解

1.内存

内存单元编号==地址==指针

2.指针变量和地址

2.1.取地址操作符(&)

&——用来获取变量地址

 #include <stdio.h>
 int main()
{
     int a = 10;
     printf("%p\n", &a);//%p打印出&a取出的地址,即a的地址
     return 0;
}

2.2.指针变量

通过取地址操作符(&)获取的地址也是一个数值,而指针变量是一种变量,用来存放地址

2.3.解引用操作符—(*)

#include<stido.h>
int main ()
{
  int a=100;
 int *p=&a;
 *pa=0;
 return 0;
}
上⾯代码中第7⾏就使⽤了解引⽤操作符, *pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0.
其实这⾥是把a的修改交给了pa来操作,这样对a的修改,就多了⼀种的途径,写代码就会更加灵活.

2.4.指针变量大小

32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

2.5.指针变量类型意义

2.5.1类型:

①整形指针 int *p

②字符指针 char *p

数组指针变量 (例如形式 int (*p)[10]; )(p要先和*结合,说明p是一个指针变量)

数组名就是数组⾸元素(第⼀个元素)的地址,本质上p[i] 是等价于 *(p+i)。

⼀维数组传参,形参的部分可以写成数组的形式,也可以写成指针的形式。

void test(int* arr)//参数写成指针形式
void test(int arr[])//参数写成数组形式

④函数指针

函数指针变量应该是⽤来存放函数地址的,函数名就是函数地址, 也可以通过 &函数名 的⽅
式获得函数的地址

2.5.2 函数指针类型解析

int (*pf3) (int x, int y)
 |    |     ------------ 
 |    |           |
 |    |     pf3指向函数的参数类型和个数的交代
 |    函数指针变量名
 pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型

2.5.2.1函数指针的应用
 #include <stdio.h>
 int Add(int x, int y)
 {
     return x+y;
 }
 int main()
 {
     int(*pf)(int, int) = Add;
     printf("%d\n", (*pf)(2, 3));
     printf("%d\n", pf(3, 5));
     return 0;
}

2.5.3指针的解引用

//代码1
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 int *pi = &n; 
 *pi = 0; 
 return 0;
}
//代码2
#include <stdio.h>
int main()
{
 int n = 0x11223344;
 char *pc = (char *)&n;
 *pc = 0;
 return 0;
}
调试我们可以看到,代码1会将n的4个字节全部改为0,但是代码2只是将n的第⼀个字节改为0。
结论:指针的类型决定了,对指针解引⽤的时候有多⼤的权限(⼀次能操作⼏个字节)。
⽐如: char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节

2.5.4 void*指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为⽆具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进
⾏指针的+-整数和解引⽤的运算。
举例
#include <stdio.h>
int main()
{
 int a = 10;
 int* pa = &a;
char* pc = &a;
 return 0;
}

上述代码,将⼀个int类型的变量的地址赋值给⼀个char*类型的指针变量,编译器会因为类型不兼容而发出警告,而使用void*类型就不会有这样的问题。

2.5.5二维数组传参

如数组 int a[3][5],形参可以写成数组,也可以写成指针形式int (*p)[5],即意味着⼆维数组传参本质上也是传递了地址,传递的是第⼀ ⾏这个⼀维数组的地址。而运用时可以写作 *(*(p+i)+j )

3.const修饰指针

3.1const修饰变量

变量是可以修改的,如果把变量的地址交给⼀个指针变量,通过指针变量的也可以修改这个变量。
但是如果我们希望⼀个变量加上⼀些限制,不能被修改,怎么做呢?这就是const的作⽤
#include <stdio.h>
int main()
{
 int m = 0;
 m = 20;//m是可以修改的
 const int n = 0;
 n = 20;//n是不能被修改的
 return 0;
}

上述代码中,n就是不可以被直接修改的,但如果绕开n,使用n的地址,去修改n就可以做到。

#include <stdio.h>
int main()
{
 const int n = 0;
 printf("n = %d\n", n);
 int*p = &n;
 *p = 20;
 printf("n = %d\n", n);
 return 0;
}

两次结果:n=0和n=20;这里看到即使变量n被const修饰,通过拿到n的地址也可以改变n的值,那要怎么做即使拿到n的地址,也不可以改变n的值呢?就到下一个知识点

3.2const修饰指针变量

const如果放在*的左边,修饰的是指针指向的内容,保证指针 指向的内容 不能通过指针来改变。
但是指针变量本⾝的内容可变。(如 const int * p = &n;
const如果放在*的右边,修饰的是指针变量本⾝,保证了 指针变量的内容 不能修改,但是指针指
向的内容,可以通过指针改变。(如 int * const p = &n;

4.指针的运算

4.1指针+-整数

案例1

#include <stdio.h>
//指针+- 整数
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 for(i=0; i<sz; i++)
 {
 printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
 }
 return 0;
}
这里可以看到, 因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。
案例2
#include <stdio.h>
int main()
{
 int n = 10;
 char *pc = (char*)&n;
 int *pi = &n;
 
 printf("%p\n", &n);
 printf("%p\n", pc);
 printf("%p\n", pc+1);
 printf("%p\n", pi);
 printf("%p\n", pi+1);
 return 0;
}
运行之后可以看出, char* 类型的指针变量+1跳过1个字节,int* 类型的指针变量+1跳过了4个字节, 这就是指针变量的类型差异带来的变化。

4.2指针-指针

用途:可以用来求字符串的长度,但只有在 同一区域同类型的指针可以作差
#include <stdio.h>
int my_strlen(char *s)
{
 char *p = s;
 while(*p != '\0' )
 p++;
 return p-s;//所得差即为字符串长度
}
int main()
{
 printf("%d\n", my_strlen("abc"));
 return 0;
}

4.3指针的关系运算

#include <stdio.h>
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9,10};
 int *p = &arr[0];
 int i = 0;
 int sz = sizeof(arr)/sizeof(arr[0]);
 while(p<arr+sz) //指针的⼤⼩⽐较
 {
 printf("%d ", *p);
 p++;
 }
 return 0;
}

5.野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

5.1成因:指针未初始化,指针越界访问,以及指针指向的空间的释放

//指针指向的空间释放
#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}//脱离函数text,n的空间就被释放了
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

5.2规避办法

5.2.1指针初始化

明确知道指针指向哪里就直接复制地址,不知道就给指针赋值NULL。(不可以赋值为0,0也是地址)

5.2.2小心指针越界

5.2.3及时置空

指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

5.2.4避免返回局部变量的地址

如造成野指针的第3个例⼦,不要返回局部变量的地址。

6.assert断言

assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报
错终⽌运⾏。这个宏常常被称为“断⾔”。
assert(p != NULL);
上面运行此行代码时, 验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序
继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
若已确认程序无误,可在头文件前,定义一个宏DEBUG,即可关闭assert()机制。

7.传址调用

举例:交换两个变量的值
#include <stdio.h>
void Swap2(int*px, int*py)
{
 int tmp = 0;
 tmp = *px;
 *px = *py;
 *py = tmp;
}
int main()
{
 int a = 0;
 int b = 0;
 scanf("%d %d", &a, &b);
 printf("交换前:a=%d b=%d\n", a, b);
 Swap1(&a, &b);
 printf("交换后:a=%d b=%d\n", a, b);
 return 0;
}
传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;

8.二级指针

用于存放指针变量的地址,

9.指针数组

是存放指针的数组,指针属组的每个元素是地址,又可以指向一块区域

9.1指针数组运用:模拟二维数组

#include <stdio.h>
int main()
{
 int arr1[] = {1,2,3,4,5};
 int arr2[] = {2,3,4,5,6};
 int arr3[] = {3,4,5,6,7};
 //数组名是数组⾸元素的地址,类型是int*的,就可以存放在parr数组中
 int* parr[3] = {arr1, arr2, arr3};
 int i = 0;
 int j = 0;
 for(i=0; i<3; i++)
 {
 for(j=0; j<5; j++)
 {
 printf("%d ", parr[i][j]);
 }
 printf("\n");
 }
parr[i]是访问parr数组的元素,parr[i]找到的数组元素指向了整型⼀维数组。上述的代码模拟出⼆维数组的效果,实际上并⾮完全是⼆维数组,因为每⼀⾏并⾮是连续的。

 11函数指针数组

即把函数的地址存到一个数组中,

int (*parr1[3])();//函数指针数组定义

parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (*)() 类型的函数指针。

11.1应用

应用:转移表

int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; 

5种情况存放在数组中

  • 13
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值