指针的运算

一、指针运算

一个数据对象的内存位置有两个重要信息:
1. 数据对象的首地址

2. 数据对象占用存储空间大小

指针类型通过下面的方式,存储这两个重要信息。

1. 指针类型通过值来保存目标数据对象的首地址

2. 类型本身标记目标数据对象的空间大小

指针类型的值存储的是内存地址。内存地址是从0开始,依次加1的整型数据。那么,我们自然会产生疑问,既然指针类型的值是整数,它是否能进行运算呢?

二、指针类型与整形进行加减

我们让指针变量从地址100开始加减,看看能不能成功。如果可以,计算后的结果是什么。

#include
intmain()
{
char* pc;
short* ps;
int* pn;
long* pl;
longlong*pll;
float* pf;
double* pd;

//  赋值为100
 pc =100;
 ps =100;
 pn =100;
 pl =100;
 pll =100;
 pf =100;
 pd =100;

//  均加1
 pc = pc +1;
 ps = ps +1;
 pn = pn +1;
 pl = pl +1;
 pll = pll +1;
 pf = pf +1;
 pd = pd +1;

//  查看值
printf("pc=%u\n", pc);
printf("ps=%u\n", ps);
printf("pn=%u\n", pn);
printf("pl=%u\n", pl);
printf("pll=%u\n", pll);
printf("pf=%u\n", pf);
printf("pd=%u\n", pd);
return0;
}

编译后,我们发现编译器提示我们,不能将int类型转换为指针类型。

这个错误应该是意料之中的,指针类型可以记录两种信息,一个是首地址,一个是目标数据空间大小。

而整型数据的值可以被当作首地址,但是目标数据空间大小却无法表示。赋值操作符两边提供的信息不 统一,那么肯定无法成功赋值。

既然无法直接赋值,我们将整型转换为对应的指针类型后,再进行赋值

int main()
{
char* pc;
short* ps;
int* pn;
long* pl;
longlong*pll;
float* pf;
double* pd;

//  现将整型转换为对应的指针类型再赋值
 pc = (char*)100;
 ps = (short*)100;
 pn = (int*)100;
 pl = (long*)100;
 pll = (longlong*)100;
 pf = (float*)100;
 pd = (double*)100;

 pc = pc +1;
 ps = ps +1;
 pn = pn +1;
 pl = pl +1;
 pll = pll +1;
 pf = pf +1;
 pd = pd +1;

printf("pc=%u\n", pc);
printf("ps=%u\n", ps);
printf("pn=%u\n", pn);
printf("pl=%u\n", pl);
printf("pll=%u\n", pll);
printf("pf=%u\n", pf);
printf("pd=%u\n", pd);
return 0;
}

现在编译可以通过了,让我们仔细观察结果。所有的指针内保存的首地址一开始均为100,加1后,现在变成了不同的值。与初始值相比,分别移动了:1244848

如果稍微数值敏感一点,你应该可以看出这些数值分别是对应的目标数据对象的空间大小。sizeof(char) =1
sizeof(short) =2
sizeof(int) =4
sizeof(long) =4
sizeof(longlong) =8
sizeof(float) =4
sizeof(double) =8

指针类型加1后,将首地址向后移动了 sizeof(目标数据对象) 字节。我们再让指针加2试试看?

 pc = pc +2;
 ps = ps +2;
 pn = pn +2;
 pl = pl +2;
 pll = pll +2;
 pf = pf +2;
 pd = pd +2;

指针的运算_第1张图片

与初始值相比,分别移动了:248816816

指针类型加2后,将首地址向后移动了两个 sizeof(目标数据对象) 字节。

 pc = pc -1;
 ps = ps -1;
 pn = pn -1;
 pl = pl -1;
 pll = pll -1;
 pf = pf -1;
 pd = pd -1;

指针的运算_第2张图片

指针类型与整型也可以进行减法运算。指针类型减1后,将首地址向前移动了 sizeof(目标数据对象)字节。

sizeof(目标数据对象) 被称作步长。

指针类型加 n 后。其首地址向后移动 n * 步长字节。
指针类型减 n 后。其首地址向前移动 n * 步长字节。

三、应用指针类型与整型加减

#include
int main()
{
int n1;
int n2;
int n3;
int n4;

int*pn1 =&n1;
int*pn2 =&n2;
int*pn3 =&n3;
int*pn4 =&n4;

printf("pn1 = %u\n", pn1);
printf("pn2 = %u\n", pn2);
printf("pn3 = %u\n", pn3);
printf("pn4 = %u\n", pn4);
return0;
}

指针的运算_第3张图片

n1n2n3n4之间的首地址刚好相差sizeof(int) 。那么我们是否可以通过指针与整型加减来访问它们呢?

int main()
{
int n1;
int n2;
int n3;
int n4;

printf("&n1=%d\n", &n1);
printf("&n2=%d\n", &n2);
printf("&n3=%d\n", &n3);
printf("&n4=%d\n", &n4);
putchar('\n');

//  获取n1的首地址
int* p =&n1;
printf("p = %d\n", p);
printf("p - 1 = %d\n", p -1);
printf("p - 2 = %d\n", p -2);
printf("p - 3 = %d\n", p -3);
putchar('\n');

//  n1n4赋值
*p =111;             //  p指向n1
*(p -1) =222;       //  p - 1后前后移动4字节,现在指向n2 *(p -2) =333;       //  p - 2后前后移动8字节,现在指向n3 *(p -3) =444;       //  p - 3后前后移动12字节,现在指向n4

printf("n1=%d\n", n1);
printf("n2=%d\n", n2);
printf("n3=%d\n", n3);
printf("n4=%d\n", n4);
return0;
}

n1的首地址为10484280
n2的首地址为10484276
n3的首地址为10484272
n4的首地址为10484268

指针 p 指向 n1

 p 的值为 n1 的首地址,即10484280

 p - 1 后,向前移动 sizeof(int) ,即10484276 p - 2 后,向前移动2 sizeof(int) ,即10484272 p - 3 后,向前移动3 sizeof(int) ,即10484268

根据首地址我们发现:
 p 指向 n1

 p - 1 指向 n2
 p - 2 指向 n3
 p - 3 指向 n4

接着,我们使用取值运算符*,将指针指向的目标数据对象进行修改。 *p = 111 ,使得 p 指向的数据对象改为111

 *(p - 1) = 222 ,使得 p - 1 指向的数据对象改为222 *(p - 2) = 333 ,使得 p - 2 指向的数据对象改为333 *(p - 3) = 444 ,使得 p - 3 指向的数据对象改为444

n1n4被修改为了111444

注意,表达式 p - 1 必须先被括号包裹,再使用取值运算符*。这是因为取值运算符*的优先级高于算术运算符。

我们需要先让首地址移动,再进行取值操作。
若不使用括号, *p 会先被取值,之后值再被加1

不使用括号:
 *p 的值为111 *p - 1 的结果为110

使用括号:
 (p - 1) 使得首地址移动到n2 *(p - 1) 得到结果为222

请格外注意,函数内声明的变量在内存中不一定是首尾相接的。受到内存对齐的影响,它们有可能不连 续。这个例子仅仅是为了让读者理解指针与整型加减运算,请勿在实际使用中使用这种写法

另外,数组可以保证各元素在内存中首尾相接,各元素连续分布在内存上。所以,C语言中可以使用指 针与整型加减运算访问和修改数组元素。

四、同类型指针减法运算

这一次,为了严格保证数据对象在内存中一定连续分布,我们使用数组来举例。

#include

int main()

{

int arr[10];

printf("&arr[0] = %d\n", &arr[0]);

printf("&arr[5] = %d\n", &arr[5]);

printf("&arr[5] - &arr[0] = %d\n", &arr[5] -&arr[0]);

return0;

}

第一个元素 arr[0] 的首地址为20970856

第六个元素 arr[5] 的首地址为20970876

 &arr[5] - &arr[0] 进行减法运算后,结果居然是5

类比于指针类型与整型加减,这里肯定也受到了步长影响。

步长为目标类型所占空间大小,步长为 sizeof(int)

 arr[5] 的首地址减 arr[0] 的首地址为20970876 - 20970856 = 20。两首地址差值除以步长刚好为5。这里的5意义为:第一个元素与第六个元素之间相隔了5个元素。

规律
 sizeof(目标数据对象) 被称作步长。

指针类型与指针类型相减后,其结果为两首地址差值除以步长。

五、其他类型的指针运算

上面我们介绍了两种有指针类型参数的运算:

1. 指针类型与整型加减。

2. 同类型的指针相减。

它们的运算结果都在内存上拥有实际的意义。

还有另外几种运算:
1. 指针类型与整型进行乘除运算。

2. 同类型的指针相加。

3. 同类型的指针乘除。

这些运算结果都没有实际意义,在C语言中无法通过编译。

你可能感兴趣的:(算法,c语言,学习)