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)解引用voidp通用类型指针时,必须要把指针转换成某种类型才能解引用
12.二级指针
存放一级指针的地址
int a = 10;
int *p = &a;
int **q = &p;
printf("p addr:%p %p\n",&p,q);