第二章 指针基础
指针:用于存储数据或函数的内存地址的变量,指针也占用内存空间
内存地址也是数字或值
2.1 变量的地址
上图表示如何用整型变量x存储值40。将值40存储在0x00394768位置,这个位置被变量x引用。
内存地址也是数字或值,我们可以用指针来存储或访问某个内存地址
2.2 地址操作符
取址操作符&返回操作数的内存地址
#include <stdio.h>
#include <stdlib.h>
int main()
{
int a;
printf("please Input value of a:");
scanf("%d",&a);
printf("a=%d",a);
printf("\naddress of a is %p\n", &a);
return 0;
}
在上面的代码中,函数scanf使用取址操作符&得到存储用户输入变量a的地址,scanf函数需要知道输入值应该存储的地址
struct node{
int a;
int b;
};
int main()
{
struct node p;
printf("Address of p is:%p",&p);
printf("\nAddress of a is:%p",&p.a);
printf("\nAddress of b is:%p",&p.b);
return 0;
}
注意第一个成员的地址与第二个成员的地址非常相近。这意味着对任意数量的结构体内的成员字段,地址按顺序或依据其大小就近分配。
2.3 指针声明
数据类型 * 变量名
例1:一个指针变量指向并存储原始数据类型的地址
int * a; char * b;
解引用操作符(*)帮助编译器识别它是一个指针变量
例2:指针变量作为结构体的数据成员
struct inneer_node{
int in_a;
int in_b;
}
struct node{
int *a;
int *b;
strucy inner_node* in_node;
}
2.4指针赋值
int *a;
指针变量声明时没有指向,程序员必须在解引用它之前让其指向有效的内存地址
使用两种方式实现指针变量指向特定的内存地址
- 1:利用指针地址&分配变量的地址
- 2:让指针变量指向来自堆的动态内存分配
情形1,程序运行时根据变量范围分配内存存储变量x的值40
情形2,存储变量值的内存显然是通过调用malloc函数从堆中返回内存来创建的。
切记任何指针变量的操作都必须在它指向一个有效内存地址的前提下完成,否则会引起分段错误。如果发生分段错误,会导致程序崩溃并最终停止运行。
2.5 指针变量的大小
在32位平台下,指针变量的大小是4字节,存储聚合数据类型地址的指针变量大小(如数组和结构体)也是4个字节,显然指针变量的内存地址大小为32位
2.6 指针解引用
使用“取值”操作符(*)可以访问指针变量的地址中存储的值,称为指针解引用
int x = 10;/*某个内存单元存储值10*/
int *ptr = &x; /*现在指针变量"ptr"正指向内存单元x=10*/
printf("Address of variable x=%p\n",&x);/*打印内存单元x的地址 */
printf("Address of variable x = %p\n", ptr); /*使用"ptr"变量,打印内存单元×的地址,它的值在内存单元"x" */
printf("value of variable x = %d\n", x);/*打印变量x的值 */
printf("value stored at address ptr = %p is %d\n", ptr ,*ptr); /*使用取值操作符(*ptr)打印内存单元x的值*/
变量ptr的值和表达式( &x)的值本质上是等价的,为变量x的内存位置,因为ptr现指向x。
利用解引用操作符(* )得到某些内存单元的值。因此,表达式* ptr 、*(&x)和x的值都为10。
2.7指针的基本用法
2.7.1传值
函数从调用者接收信息并将结果返回给调用者
值传递后将复制到被调用的相应栈中
2.7.2引用传递
通过引用传递传递变量的内存地址而不是变量值本身
通过引用技术有可能在不同的函数中对某个函数的局部变量进行操作
2.8指针和常量
普通的const变量的含义是初始化时的赋值在其生存期内不能修改
2.8.1常量指针变量
//这一块可能于其他书籍或网页的定义有歧义
常量指针是一个仅指向唯一内存地址的指针变量,因此,指针变量的值不能修改
常量指针声明
int * const a;
常量指针变量声明时必须初始化,一旦完成初始化,常量指针不能再指向其他内存地址
2.8.2 常量指针
常量指针是指某个指针变量的值(即变量的内存地址)不能修改指定内存地址存放的值
常量指针声明
const int * a;
2.8.3 指针常量
不能修改指针变量的值(即不能指向其他内存单元),也不能修改存储在改地址的值
指针常量声明:
const int * const a;
2.9 多级指针
指针变量本身的地址可以存在其他变量中
声明:
int ** a;
2.10理解神秘的指针表达式
2.10.1 一级指针应引用
int val=10;
int *ptrVal=&val
ptrVal=&val,都是保存的val的地址
2.10.2一级指针解引用
*ptrval=10
*(&val)=10
*ptrVal=*(&val),都是保存的val的值
2.10.3二级指针引用
int** ptrptrvar=&ptrvar
ptrptrvar是一个指向指针变量的指针
二级指针 ptrptrvar存储的是指针变量ptrvar的地址
ptrptrvar=&ptrvar
2.10.4 二级指针解引用
ptrptrvar得到的是ptrvar的地址存放的值(ptrvar的地址存放的值是一个ptrvar指针)
所以ptrptrvar=ptrvar
根据我自己的总结,一个*可以抵消一个&
第三章 指针运算与一维数组
指针变量能做算术运算操作
3.1 数组内存排列
字节序
字节序描述数据将存储在内存时的格式/排列。加载/存储指令从内存读取数据,在运行完数据处理指令后从寄存器将数据写回内存。
在存储和加载时,CPU必须采用硬件支持的字节序格式。
字节序分两类:大端和小端。一个字长为4字节/32位。
3.2指针运算
指针运算可以使用++,+,- -,-但是不能使用/和*
3.2.1指针加法
#include <stdio.h>
#include <stdlib.h>
int main()
{
int arr[4]={1,2,3,4};
printf("%d\n",arr[0]);
printf("%p\n",arr);
int*ptr=&arr;
printf("int指针\n",arr);
for(int i=0;i<4;i++)
{
printf("%p\n",ptr);
ptr++;
}
char*ptr2=(char*)&arr[0];
printf("char指针\n",arr);
for(int j=0;j<4;j++)
{
printf("address:%p value:%x\n",ptr2,*ptr2);
ptr2++;
}
return 0;
}
从以上代码可以看出,数组名指向数组的首地址
由于Int占4个字节,所以ptr++一次跳过4个字节,指向数组中的下一个元素且指向当前元素的第一个字节
由于char占1个字节,所以,可以把一个Int型数据的地址的首个字节用char*来指向
指针变量加偏移量是非常合法的操作,但不允许将指针变量加入另一个指针变量中
int *ptr;int *ptr1;
ptr=ptr+1;//合法
ptr=prtr+ptr2;//不合法
3.2.2指针减法
int arr[4]={1,2,3,4};
printf("%d\n",arr[0]);
printf("%p\n",arr);
int*ptr=&arr[3];
printf("int指针\n",arr);
for(int i=3;i>=0;i--)
{
printf("%p\n",ptr);
ptr--;
}
两个指针变量的减法
当两个指针分别指向数组连续内存地址的不同位置,让他们彼此相减得到的结果差表示两个指针变量之间存在的元素数目
上述代码段中,int * ptr1指向第0个索引,int*ptr2指向第1个索引。当两个变量彼此相减后结果为1,因为这两个连续位置之间只存在一个元素。
比较两个指针变量
ptr1==ptr2
ptr1<ptr2
ptr1>ptr2
3.3数组探究
数组名等同于指定数组的第0个位置的地址,加偏移量之后数组名的作用相当于指针变量
int arr[4]={1,2,3,4};
printf("%p",arr);
for(int i=0;i<4;i++)
{
printf("%p",arr++);
}
不能用arr=arr+1,因为这样就试图修改arr的指向了,这是不可以的
可以用arr+1指向下一个元素的地址
arr++也是不允许的,因为这试图修改数组变量的起始地址
arr+2为第二个索引的地址等价于&arr[0]+2
即
arr+offset=&arr[offset];
*(arr+offset)=arr[offset];
3.3.1动态数组
有时候定义数组时不知道要存储的数组元素数目,程序运行时,数组元素数目可能增多也可能减少,所以咱们写程序时应当让数组的大小在运行时可变
int arr[10]:静态数组,运行时大小不能该改变
指针能帮助我们操作内存区域实现根据需求增加或降低内存,利用指针和堆实现该目标,调用malloc()在堆中分配内存
#include <stdio.h>
#include <stdlib.h>
int *ptr=NULL;
static int count=0;
void insert(int data)
{
if(ptr==NULL)
{
ptr=(int*)malloc(sizeof(int));//从堆分配空间给第一个data单元
ptr[0]=data;//利用数组符号访问内存地址来存储数据
}
else
{
ptr=(int*)realloc(ptr,sizeof(int)*(count+1));//增加内存大小
ptr[count]=data;//利用数组符号访问内存地址来存储数据
}
count++;
}
void show()
{
for(int i=0;i<count;i++)
{
printf("%d\n",ptr[i]);
}
}
int main()
{
int c=0;
int data;
while(c!=3)
{
printf("Insert choice:\n");
printf("1 to insert data\n");
printf("2 to show data\n");
printf("3 to quit data\n");
scanf("%d",&c);
if(c==3)
{
break;
}
switch(c)
{
case 1:
printf("Data=");
scanf("%d",&data);
insert(data);
break;
case 2:
printf("Data in Array\n");
show();
break;
}
}
return 0;
}
在上述代码中,the insert(int data)函数插入第一个元素。调用malloc()函数申请内存并将指针变量ptr指向从堆中返回的内存位置。在代码中,每次只能添加一个数据元素,所以它只需要一个存储空间。
增加内存分配空间给新的数据元素。调用realloc()函数创建数据所需的内存空间并将返回值赋值给指针变量ptr。realloc()调用函数包括期望增加内存空间大小的参数。
变量count统计已插入元素的数目。注意,分配内存大小相对于已插入元素是额外的。
ptr不是一个指针变量吗,怎么变成数组了?
参考:指针和数组不一样
3.3.2指针数组
指针数组是指在连续单元内存中存储指针变量,该数组中每个位置包含内存中某个数据的地址
指针数组声明:
int * arr[10];
#include <stdio.h>
int main(int argc,char* argv[]){
int arr[4] = {1,2,3,4};
int* arr_ptr[4];
int i;
for(i = 0; i<4; i++){
arr_ptr[i] = arr + i;
}
printf("Address of (arr) array element\n");
for(i = 0; i<4; i++)
{
printf( "Address of %d index = %p\n",i, arr + i);
}
printf( "value of (arr_ptr) array of pointer element\n");
for(i = 0; i<4; i++)
{
printf("value of %d index = %p\n",i,arr_ptr[i]);
}
return 0;
}
3.3.3数组指针
数组指针为指向数组的指针变量
数组指针声明
#include <stdio.h>
int main(int argc,char* argv[]){
int arr[4]= {1,2,3,4};
int(*ptr2arr)[4];
int i;
int *ptr = arr;
ptr2arr = &arr;
for(i =0; i<4; i++){
printf("address of array = %p\n", arr + i);
}
for(i = 0; i<4; i++)
{
printf("Value at %p = %d\n" , (ptr2arr[0] + i),*(ptr2arr[0] + i));
}
return 0;
}
在上述代码中,ptr2arr为数组指针,指向存储四个整型类型数据元素的数组。
因为我们代码中用的%d,所以16进制地址转化成了10进制的地址
关于 *p打印出来的结果竟然是一个地址值这种情况,下面这篇博客有说到