C语言指针

1.内存地址

字节:字节是内存的容量单位,英文称为byte,一个字节有8位,即1byte = 8bits
地址:系统为了便于区分每一个字节而对它们逐一进行的编号,称为内存地址,简称地址。
在这里插入图片描述

2.基地址

单字节数据:对于单字节数据而言,基地址就是其字节编号。
多字节数据:对于多字节数据而言,基地址是其所有字节中编号最小的那个,称为基地址。
在这里插入图片描述

3.取址符

每个变量都是一块内存,都可以通过取址符&获取其地址。例如:

int a = 100;
printf("整型变量 a 的地址是: %p\n", &a);

char c = 'x';
printf("字符变量 c 的地址是: %p\n", &c);

double f = 3.14;
printf("浮点变量 f 的地址是: %p\n", &f);
  • 注意:
    • 虽然不同的变量的尺寸是不同的,但是他们的地址的尺寸确实一样的。
    • 不同的地址虽然形式上看起来是一样的,但由于他们代表的内存尺寸和类型都不同,因此它们在逻辑上是严格区分的。

4.指针变量

一个专门用来存放内存地址的变量,指针也就是指针变量

1)格式
类型 *指针变量名;

解释:
“类型”:指针变量指向的内存空间存放的数据类型。
“指向”:如果我保存了你的地址,那么就说我指向你
“*”:定义指针变量的固定格式

int a = 20;
int *p = &a;   &a== 0x00239332  p == 0x00239332
//指针变量p指向变量a所在的那片内存空间
(第一部分)*p  :马上在内存中申请8个字节,用于存放64位地址
(第二部分)int : 地址所指向的数据是什么类型的

2)大小
在32位处理器中,指针的大小都是4个字节
在64位处理器中,指针的大小都是8个字节

5.指针的运算

int a;//在内存中申请4个字节的空间,由变量名a间接访问
int *p;//在内存中申请8个字节的空间,存放一个地址,这个地址所指向的内容是int类型
p = &a; //&:取地址符,&a获取a变量的地址,把这个地址存放在指针变量p中
*p  // *:解引用,访问该指针指向的目标,  a值与 *p的值
*p = *&a) = a;//取地址符与解引用是逆运算

指针的加减称之为指针的偏移,偏移的单位由指针的类型大小所决定
所谓的类型大小指的是指针变量所指向的内存空间的数据类型
示例案例

//定义一个指针存储 数组的名字, 通过该指针 获取  单个字符 w    
char str[ ] = "hello world";
char *p;
    
p = str;
printf("%c\n",*(p+6));
//将所有获取到单个字符w的方法都罗列出来

6.字符串常量

"hello”是字符串常量,存储在内存空间的数据段中,同时,"hello”也是一个匿名的内存空间的首地址('h’的地址)

1.字符串常量
"hello”是字符串常量,存储在内存空间的数据段中,同时,"hello”也是一个匿名的内存空间的首地址('h'的地址)

在这里插入图片描述

char string [3][10] = {"hello","world","www"};

在这里插入图片描述
练习1:
画出以下数组的内存图,并且标明数组的名字表示的地址范围

char str1[] = "zouwei!";
char str2[][10]= {"nihao","luban","gebiwang5"};
char *str3[3]= {"nihao","luban","gebiwang5"};

练习2:以下两种方式有没有区别,是否正确???

char *str1[3];
strcpy(str1[0],"zhang3");
char str2[3][10];
strcpy(str2[0],"zhang3");

7.字符串与指针

在C语言中,并没有内置字符串的类型,c语言的字符串是通过char*指针来实现的。

char *str = "ABCDEF";
//str:保存字符串的首地址,即字符'A'的地址
 //str+1:指向字符'B'的地址
*(str+1) = '2';//error,因为这里没有定义字符数组来保存,即没有分配空间,常量
            //保存在rodata段内,是不能被改变的。    

"ABCDEF"在C语言中,把该字符串的首地址赋值给str,由于字符串必有终止符,
系统可以把字符串的内容完成读出来。
注意:

int *p,a;   // a是int类型的变量
如果想要把a也定义成指针,可以这样写:
int *p,*a;
printf("p size:%d a size:%d\n",sizeof(p),sizeof(a));

8.指针与数组的转换

1、数组指针

//一维数组
int buf[3] = {10,20,30};

int *p = buf;//buf是数组名,代表最大个元素的地址类型
//这个时候,p完全等价于数组名buf

printf("buf[0] = %d\n",*p);
printf("buf[0] = %d\n",p[0]);
printf("buf[0] = %d\n",*buf);
printf("buf[0] = %d\n",*(buf+0));

printf("buf[1] = %d\n",*(p+1));
printf("buf[1] = %d\n",p[1]);

printf("buf[1] = %d\n",*(buf+1));

//数组指针:首先是指针,用来存储数组的地址的指针类型
int (*q)[3] = &buf;//数组名取地址,代表整个数组的地址类型,也代表整个数组的范围

//如何通过q访问数组中的元素
printf("buf[0]=%d\n",**q);//*q表示数组中首元素的地址
printf("buf[1]=%d\n",*(*q+1));
printf("buf[2]=%d\n",*(*q+2));//*q+2表示从首元素的地址开始,向右偏移2个单位,也就是达到buf[2]的地址,如果想要引用buf[2]中的数据,必须还要解引用

练习:定义一个指针,存储数组array的名字,罗列出 通过数组名字 和 指针 获取400数据的所有方法

int  array[6] = {100,200,300,400,500,600};
//二维数组
int buf[2][3] = {    {11,22,33},
             {44,55,66}};

//定义一个指针,存储二维数组的名字
int (*p)[3] = buf;
//通过数组名或者指针变量p访问数组中的元素11
//1)通过数组名去访问    
printf("buf[0][0]=%d\n",**buf);
printf("buf[0][0]=%d\n",*(buf[0]+0));
printf("buf[0][0]=%d\n",buf[0][0]);
//2)通过指针变量去访问    
printf("buf[0][0]=%d\n",**p);
printf("buf[0][0]=%d\n",*(p[0]+0));
printf("buf[0][0]=%d\n",p[0][0]);

//通过数组名或者指针变量p访问数组中的元素55
//1)通过数组名去访问    
printf("buf[1][1]=%d\n",*(*(buf+1)+1));
printf("buf[1][1]=%d\n",*(buf[1]+1));
printf("buf[1][1]=%d\n",buf[1][1]);
//2)通过指针变量去访问    
printf("buf[1][1]=%d\n",*(*(p+1)+1));
printf("buf[1][1]=%d\n",*(p[1]+1));
printf("buf[1][1]=%d\n",p[1][1]);

//二维数组的名字取地址
int (*q)[2][3]  = &buf;

练习:判断下面是数组还是指针,如果是指针,那么可以存储什么类型的数组地址

char *p[3]
char *(p[3])
char (*p)[3]
char *p[2][3]

总结:
1、在定义语句中,判断这个变量是数组还是指针,通过运算符的优先级判断: () > [ ] > *
如果变量首先粘住的是[ ],那么这个变量一定是一个数组
如果变量首先粘着的是* ,那么这个变量一定是一个指针

2、判断一个指针变量的类型,变量名遇到*号的时候,记住,一定要停下来,剩余的东西都是用来修饰这个指针所指向的内容是怎么样的
比如:char *(*p)[3] = &buf; //p —指针数组指针
int (*p)(int,int) = add_func;// p—函数指针

9.野指针

  • 概念:指向一块未知区域的指针,被称为野指针。野指针是危险的。
  • 在这里插入图片描述
  • 危害:
    a.引用野指针,相当于访问了非法的内存,常常会导致段错误(segmentation fault)
    b.引用野指针,可能会破坏系统的关键数据,导致系统崩溃等严重后果
  • 产生原因:
    a.指针定义之后,未初始化
    b.指针所指向的内存,被系统回收
    c.指针越界
  • 如何防止:
    a.指针定义时,及时初始化
    b.绝不引用已被系统回收的内存
    c.确认所申请的内存边界,谨防越界
int a;//a就会随机赋值 
int *p;//p就是野指针
printf("p:%d\n",*p); ----可能会发生段错误,也可能引用了其他内存空间的数据

10.空指针

很多情况下,我们不可避免地会遇到野指针,比如刚定义的指针无法立即为其分配一块恰当的内存,又或者指针所指向的内存被释放了等等。一般的做法就是将这些危险的野指针指向一块确定的内存,比如零地址内存。
在这里插入图片描述

  • 概念:空指针即保存了零地址的指针,亦即指向零地址的指针。
  • 示例:
NULL是一个宏定义,在Ubuntu中头文件:/usr/lib/gcc/x86_64-linux-gnu/7/include/stddef.h

#define NULL 0
#else
#define NULL ((void *)0)
#endif

一般地,为了防止出现野指针,一般都会给指针赋值NULL
int *p = NULL;

11.通用类型的指针 void*

通用类型的指针:指针可以指向任何类型的数据类型,但是在解引用时,必须把地址强制转换为某种具体的类型才能进行解引用。

int a = 10;
//通用型指针p,可以指向任何类型的数据类型
void *p = &a;
//但是在解引用的时候,必须进行强制类型转换为某种具体的数据类型
//printf("%d\n",*p);
//错误
/*
warning: dereferencing ‘void *’ pointer
*/
printf("%d\n",*(int*)p);

小结:
1)在定义指针变量后,一定要赋值安全区域或者NULL,防止指针“乱指”
2)只有void p ,没有void a
3)解引用void
p通用类型指针时,必须要把指针转换成某种类型才能解引用
在这里插入图片描述

12.二级指针

存放一级指针的地址
int a = 10;
int *p = &a;
int **q = &p;

printf("p addr:%p %p\n",&p,q);

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值