c语言指针基础看这一篇就够了

一,什么是指针

              内存中每个用于 数据 存取的 基本单位 ,都被赋予一个唯一的 序号 ,称为地址。

              C语⾔中给地址起 了新的名字叫:指针。 我们可以理解为: 内存单元的编号 == 地址 == 指针。

二,取地址符号(&),解引⽤操作符(*)和指针变量

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

上述的代码就是创建了整型变量a,内存中 申请4个字节,⽤于存放整数10,要想得到a的地址,就需要用取地址符来得到地址,然后用指针变量(类型 * 变量名)来储存地址,再用%p打印。

把地址保存起来,未来是要使⽤的,那怎么使⽤呢? 在现实⽣活中,我们使⽤地址要找到⼀个房间,在房间⾥可以拿去或者存放物品。 C语⾔中其实也是⼀样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

#include <stdio.h>
int main()
{
 int a = 100;
 int* pa = &a;
 *pa = 0;
 return 0;
}

*pa 的意思就是通过pa中存放的地址,找到指向的空间, *pa其实就是a变量了;所以*pa = 0,这个操作符是把a改成了0.

三,指针大小

#include <stdio.h>
//指针变量的⼤⼩取决于地址的⼤⼩
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
 printf("%zd\n", sizeof(char *));
 printf("%zd\n", sizeof(short *));
 printf("%zd\n", sizeof(int *));
 printf("%zd\n", sizeof(double *));
 return 0;
}
结论:
• 32位平台下地址是32个bit位,指针变量⼤⼩是4个字节
• 64位平台下地址是64个bit位,指针变量⼤⼩是8个字节
• 注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

四,指针加减整数

#include <stdio.h>
int main()
{
		int n = 10;
		char* pc = (char*)&n;
		int* pi = &n;
		printf("&n=%p\n", &n);
		printf("pc=%p\n", pc);
		printf("pc+1=%p\n", pc + 1);
		printf("pi=%p\n", pi);
		printf("pi+1=%p\n", pi + 1);
		return 0;
	}

运行结果

可以看出, char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
这就是指针变量的类型差异带来的变化。

五,指针运算

5.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;
}

运行结果:

p+0得到1

p+1得到2

p+2得到3

............

 5.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; 
}

运行结果:

 指针减指针得到的是指针之间元素的个数,因此可以用来模拟库函数strlen()

5.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; }

运行结果:

arr是第一个元素地址,sz是数组大小为10,arr+10就是元素10后面一个元素

同时p也会不断加1,这时候p<arr+sz不成立了,就不会打印了

六,野指针

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

6.1野指针成因

1.指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
#include <stdio.h>
int main()
{
 int arr[10] = {0};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<=11; i++)
 {
 //当指针指向的范围超出数组arr的范围时,p就是野指针
  *(p++) = i;
 }
return 0;
}
3.指针指向的空间释放
#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}

int main()
{
 int*p = test();
 printf("%d\n", *p);
return 0;
}

6.2如何规避野指针

1.指针初始化
如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL.。NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。
#include <stdio.h>
int main()
{
 int num = 10;
 int*p1 = &num;
 int*p2 = NULL;
 
 return 0; 
}
2.小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是越界访问
3.及时置NULL

指针不用时及时置NULL,使用前检查有效性【if(p!=NULL){}

4.避免返回局部变量的地址

例子看6.1.3test()函数

七,关于数组名的理解

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("arr = %p\n", arr);
 return 0; 
}

可以发现数组名就是数组⾸元素(第⼀个元素)的地址。

---------------------------------------------------------------------------------------------------------------------------------

接下来看这段代码:

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("%d\n", sizeof(arr));
 return 0;
}

 输出的结果是:40,如果arr是数组⾸元素的地址,那输出应该的应该是4/8才对。

其实数组名就是数组⾸元素(第⼀个元素)的地址是对的,但是有两个例外:

• sizeof(数组名),sizeof中单独放数组名,这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩,单位是字节。

• &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址(整个数组的地址和数组⾸元素的地址是有区别的)。

除此之外,任何地⽅使⽤数组名,数组名都表示首元素的地址。
---------------------------------------------------------------------------------------------------------------------------------
再试⼀下这个代码:
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("&arr[0] = %p\n", &arr[0]);
printf("arr = %p\n", arr);
printf("&arr = %p\n", &arr);
return 0;
}

你会发现结果一样

那arr和&arr有什么区别?

#include <stdio.h>
int main()
{
 int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
 printf("&arr[0] = %p\n", &arr[0]);
 printf("&arr[0]+1 = %p\n", &arr[0]+1);
 printf("arr = %p\n", arr);
 printf("arr+1 = %p\n", arr+1);
 printf("&arr = %p\n", &arr);
 printf("&arr+1 = %p\n", &arr+1);

 return 0; 
}

你会发现,&arr[0]和&arr[0]+1相差4个字节,arr和arr+1 相差4个字节,是因为&arr[0] 和 arr 都是
⾸元素的地址,+1就是跳过⼀个元素。
但是&arr 和 &arr+1相差40个字节,这就是因为&arr是数组的地址,+1 操作是跳过整个数组的。

 八,二级指针

#include <stdio.h>
int main()
{
	int a=10;
    int* p=&a;//一级指针
    int** pa=&pa;//二级指针
}
对于⼆级指针的运算有:
*pa 通过对ppa中的地址进⾏解引⽤,这样找到的是 *pa 其实访问的就是 .
int b = 20 ;
*pa = &b; // 等价于 pa = &b;
**pa 先通过 *pa 找到 p   ,然后对 p 进⾏解引⽤操作: *p  ,那找到的是 a .
**pa = 30 ;
// 等价于 *pa = 30;
// 等价于 a = 30;

 八,指针数组

指针数组的每个元素都是⽤来存放地址(指针)的。

指针数组的每个元素是地址,⼜可以指向⼀块区域。

九,字符指针变量

int main()
{
 char ch = 'w';
 char *pc = &ch;
 *pc = 'w';
 return 0;
}
还一种使用方法:
int main()
{
 const char* pstr = "hello world";//这⾥是把⼀个字符串放到pstr指针变量⾥了吗?
 printf("%s\n", pstr);
 return 0;
}

const char* pstr = "hello bit."; 特别容易以为是把字符串 hello bit 放 到字符指针 pstr ⾥了,但是本质是把字符串 hello bit. ⾸字符的地址放到了pstr中。

十,数组指针变量

• 整形指针变量: int * pint; 存放的是整形变量的地址,能够指向整形数据的指针。

• 浮点型指针变量: float * pf; 存放浮点型变量的地址,能够指向浮点型数据的指针。

那数组指针变量应该是:存放的应该是数组的地址,能够指向数组的指针变量

int arr[10] = {0}; 

int(*p)[10] = &arr;//数组指针

注意区别数组指针和指针数组

十一,函数指针变量

#include <stdio.h>
int Add(int x, int y)
{
 return x+y;
}

int main()
{
 int(*pf3)(int, int) = Add;
 
 printf("%d\n", (*pf3)(2, 3));
 printf("%d\n", pf3(3, 5));

 return 0; 
}

结果是 5  8。

十二,函数指针数组

int (*par[3])();

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

 基础知识就到结束了,还有的期待我下次分享吧。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值