回顾
程序中的变量只是一段存储空间的别名,那么是不是必须通过这个别名才能使用这段存储空间?
**一、思考**
这段程序输出什么?
int i = 5;
int* p = &i;
printf("%d,%p\n", i, p);
*p = 10;
printf("%d,%p\n", i, p);
*号的意义
一、在指针声明时,*号表示所声明的变量为指针
二、在指针使用时,*号表示取指针所指向的内存空间中的值
int i = 0;
int j = 0;
//指针声明
int* p = &i;
//取值
j = *p;
*号就好比一把钥匙,通过这边钥匙可以打开内存,并读取内存中的值;
传值调用与传址调用
一、指针是变量,因此可以声明指针参数
二、当一个函数内部需要改变实参的值时,则需要使用指针参数
三、函数调用时实参值将复制到形参
四、指针适用于复杂数据类型作为参数的函数
例子
1、传值调用
#include <stdio.h>
int swap(int a, int b)
{
int c = 0;
//典型的传值调用
c = a;
a = b;
b = c;
}
int main()
{
int aa = 1;
int bb = 2;
printf("aa = %d, bb = %d\n", aa, bb);
swap(aa, bb);
printf("aa = %d, bb = %d\n", aa, bb);
return 0;
}
2、传址调用
#include <stdio.h>
int swap(int* a, int* b)
{
int c = 0;
//典型的传址调用
c = *a;
*a = *b;
*b = c;
}
int main()
{
int aa = 1;
int bb = 2;
printf("aa = %d, bb = %d\n", aa, bb);
swap(&aa, &bb);
printf("aa = %d, bb = %d\n", aa, bb);
return 0;
}
常量与指针
const int* p; //p可变,p指向的内容不可变
int const* p; //p可变,p指向的内容不可变
int* const p; //p不可变,p指向的内容可变
const int* const p //p不可变,p指向的内容不可变
口诀:左数右指
当const出现在 * 号的左边时指针指向的数据为常量;
当const出现在 * 号的右边时指针本身就为常量;
#include <stdio.h>
int main()
{
int i = 0;
const int* p1 = &i;
int const* p2 = &i;
int* const p3 = &i;
const int* const p4 = &i;
*p1 = 1; //compile error;
p1 = NULL; //ok
*p2 = 2; //compile error;
p2 = NULL; //ok
*p3 = 3; //ok
p3 = NULL; //compile error;
*p4 = 4; //compile error;
p4 = NULL; //compile error;
return 0;
}
数组的概念
一、数组是相同类型的变量的有序集合
**数组示意图:定义了一个数组a[5],数组包含5个int类型的数据**
| a[0] | a[1] | a[2] | a[3] | a[5] |
|---|
这个表格中代表的是int a[5];有五个int类型的数据,并且每个元素都是int类型的数据;可以通过这个表格中可以看出有20个字节的空间的名字为a;a[0]、a[1]等元素都是a中的元素,并非元素的名字;数组中的元素是没有名字的。
其中a代表的数组第一个元素的起始地址。
数组的大小
一、数组在一片连续的内存空间中存储元素
二、数组元素的个数可以是显示或隐式指定
int a[5] = {1, 2};
int b[ ] = {1, 2}
数组地址与数组名
一、数组名代表数组首元素的地址
二、数组的地址需要用取地址符&才能得到
三、数组首元素的地址与数组的地址值相同;
四、数组首元素的地址与数组的地址是两个不同的概念
#include <stdio.h>
int main()
{
int a[5] = {0};
int b[] = {1, 2};
printf("a = %p\n", a);
printf("&a = %p\n", &a);
printf("&a[0] = %p\n", &a[0]);
return 0;
}
数组的盲点
一、数组名可以看做一个常量指针
二、数组名 “指向” 的是内存中数值首元素的起始位置
三、数组名不包含数组的长度信息
四、在表达式中只能作为右值使用
五、只有在下列场合中数组名不能看着常量指针
1、数组名作为sizeof操作符的参数
2、数组名作为&运算符的参数
#include <stdio.h>
int main()
{
int a[5] = {0};
int b[2];
int* p = NULL;
p = a;
printf("a = %p\n", a);
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("sizeof(a) = %d\n", sizeof(a));
printf("sizeof(p) = %d\n", sizeof(p));
printf("\n");
p = b;
printf("b = %p\n", b);
printf("p = %p\n", p);
printf("&p = %p\n", &p);
printf("sizeof(b) = %d\n", sizeof(b));
printf("sizeof(p) = %d\n", sizeof(p));
//b = a; //数组名只能作为右值(可以看成常量指针)
return 0;
}
小结
一、数组是一片连续的内存空间
二、数组的地址和数组首元素的地址的意义不同
三、数组名在大多数情况下被当成常量指针处理
四、数组名其实并不是指针,不能将其等同于指针
数组的本质
一、数组是一片连续的内存空间
二、数组的空间大小为sizeof(array_type)* array_size
三、数组名可以看作指向数组第一个元素的常量指针
#include <stdio.h>
int main()
{
int a[5] = {};
int* p = NULL;
printf("a = 0x%X\n", (unsigned int)(a)); //a = 0x53208C10
printf("a + 1 = 0x%X\n", (unsigned int)(a + 1)); //a + 1 = 0x53208C14
printf("p = 0x%X\n", (unsigned int)(p)); // p = 0x00
printf("p + 1 = 0x%X\n", (unsigned int)(p + 1)); // p + 1 = 0x04
return 0;
}
指针的运算
一、指针是一种特殊的变量,与整数的运算规则为
**p + n; <- ->(unsigned int)p +n*sizeof(*p);**
结论:
当指针p指向一个同类型的数组的元素时:p + 1将指向当前元素的下一个元素;
p - 1将指向当前元素的上一个元素。
二、指针之间只支持减法运算,参与减法的运算的指针类型必须相同;
p1-p2; <- ->((unsigned int)p1 -(unsigned int)p2)/sizeof(type))
注意:
1、只有当两个指针指向同一个数组的元素时,指针相减才有意义,其意义为指针所指元素的下标;
2、当两个指针指向的元素不在同一个数组中时,结果未定义;
三、指针的比较
1、指针也可以进行关系运算(<、<=、>、>=)
2、指针关系运算的前提是同时指向同一个数值中的元素;
3、任意两个指针之间的比较运算(==,!=)无限制;
4、参与比较运算的指针类型必须相同;
例题1
int main()
{
char s1[] = {'H', 'e', 'l', 'l', 'o'};
int i = 0;
char s2[] = {'W', 'o', 'r', 'l', 'd'};
char* p0 = s1;
char* p1 = &s1[3];
char* p2 = s2;
int* p = &i;
printf("%d\n", p0 - p1);
printf("%d\n", p0 + p2);
printf("%d\n", p0 - p2);
printf("%d\n", p0 - p);
printf("%d\n", p0 * p2);
printf("%d\n", p0 / p2);
}
编译结果;

例题2
#include <stdio.h>
#define DIM(a) (sizeof(a) / sizeof(*a))
int main()
{
char s[] = {'H','e', 'l', 'l', 'o'};
char* pBegin = s;
char* pEnd = s + DIM(s);
char* p = NULL;
printf(" pBegin = %p\n", pBegin);
printf(" pEnd = %p\n", pEnd);
printf("Size: %ld\n", pEnd - pBegin);
for(p = pBegin; p<pEnd; p++)
{
printf("%c", *p);
}
printf("\n");
return 0;
}
运行结果

指针和数组分析
下标形式VS指针形式
一、指针以固定增量在数组中移动时,效率高于下标形式;
二、指针增量为1且硬件具有硬件增量模型时,效率更高;
三、下标形式与指针形式的转换
**a[n] <--> *(a + n)<--> *(n + a) <-->n[a]**
注意:
现代编译器的生成代码优化率已经大大提高,在固定增量时,下标形式得到效率已经和指针形式相当;但从可读性和代码维护的角度来看,下标形式更优。
#include <stdio.h>
int main()
{
int a[5] = {0};
int* p = a;
int i = 0;
for(i=0; i<5; i++)
{
p[i] = i + 1;
}
for(i=0; i<5; i++)
{
printf("a[%d] = %d\n", i, *(a + i));
}
printf("\n");
for(i=0; i<5; i++)
{
i[a] = i + 10;
}
for(i=0; i<5; i++)
{
printf("p[%d] = %d\n", i, p[i]);
}
return 0;
}
运行结果

数组参数
一、数组作为函数参数时,编译器将其编译成对应的指针
void f(int a[]); <--> void f(int* a)
void f(int a[5]); <--> void f(int* a)
结论:
一般情况下,当定义的函数中有数组参数时,需要定义另一个参数来标示数组的大小。
总结:
1、数组名和指针使用方式相同
1)数组的本质不是指针;
2)指针的本质不是数组;
2、数组名并不是数组的地址,而是数组首元素的地址;
3、函数的数组参数退化为指针。
#include <stdio.h>
void func1(char a[5])
{
printf("In func1: sizeof(a) = %d\n", sizeof(a)); //大小为4个字节
*a = 'a';
a = NULL;
}
void func2(char b[])
{
printf("In func2: sizeof(b) = %d\n", sizeof(b)); //大小为4个字节
*b = 'b';
b = NULL;
}
int main()
{
char array[10] = {0};
func1(array);
printf("array[0] = %c\n", array[0]); //打印为0
func2(array);
printf("array[0] = %c\n", array[0]); //打印为0
return 0;
}
编译是通过的
本文详细探讨了C语言中指针与数组的本质及应用,包括指针的基本概念、运算规则、数组与指针的关系,以及它们在函数参数、内存操作中的作用。通过实例对比了指针与下标形式的效率,解析了数组参数退化为指针的现象。
327

被折叠的 条评论
为什么被折叠?



