目录
一、指针的本质分析
1、*号的意义
(1)指针的声明和使用
- 在指针声明时,*号表示所声明的变量为指针
- 在指针使用时,*号表示取指针所指向的内存空间中的值
- *号类似一把钥匙,通过这把钥匙可以打开内存,读取内存中的值。
int i = 0;
int j = 0;
//指针声明
int* p = &i;
//取值
j = *p;
//赋值
*p = 10;
图解如下:
(2)实践:指针使用示例
- 指针变量所占内存都是4字节(32位系统)
- 指针类型决定访问内存时的长度范围
内存是由字节组成的,每个字节都有一个编号。指针变量主要是存放相同数据类型的变量的首地址。这里的这个地址其实就是内存的某个字节的编号。而这个编号的确定是与地址总线有关。如果地址总线是32位,则它的寻址范围是0~2^32(0~4G)。那么为一个字节的编址就会由32个0或者1组成。例如第一个字节的编址是32个0,最后一个的编址是32个1。一个字节有8位,32位则需要4个字节。
简单的说32位的操作系统就是指:地址总线是32位的系统。那么,也就是说操作系统的位数决定了指针变量所占的字节数。
#include <stdio.h>
int main()
{
int i = 0;
int* pI;
char* pC;
float* pF;
pI = &i; // 将i的地址赋值给pI
*pI = 10; // 间接将10赋值给i
printf("%p, %p, %d\n", pI, &i, i); // i的地址 i的地址 i的值
printf("%d, %d, %p\n", sizeof(int*), sizeof(pI), &pI); // 4 4 pI的地址
printf("%d, %d, %p\n", sizeof(char*), sizeof(pC), &pC); // 4 4 pC的地址
printf("%d, %d, %p\n", sizeof(float*), sizeof(pF), &pF);// 4 4 pF的地址
return 0;
}
2、传值调用与传址调用
(1)什么是传值调用,传址调用?
- 指针是变量,因此可以声明指针参数;
- 当一个函数体内部需要改变实参的值,则需要使用指针参数;
- 函数调用时实参值将复制到形参;
- 指针适用于复杂数据类型作为参数的函数中;
- 传值调用并不改变传入的变量;
- 传址调用是传入变量地址,从而可以改变传入的地址对应的值。
(2)实践:利用指针交换变量
① 传值调用:这部分代码并不能够实现交换变量的值,这就是传值调用。
// 这部分代码并不能够实现交换变量的值,这就是传值调用
#include <stdio.h>
int swap(int a, int b)
{
int 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;
}
② 传址调用:利用指针交换变量
#include <stdio.h>
int swap(int* a, int* b) // 传入指针
{
int 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;
}
3、常量与指针
(1)const与指针
const int* p; // p可变,p指向的内容不可变
int const* p; // p可变,p指向的内容不可变
int*const p; // p不可变,p指向的内容可变
const int* const p; // p和p指向的内容都不可变
方法:不看数据类型,如果是*p代表指向的地址的数值不能变,如果是p代表地址不能变。
口诀∶左数右指
- 当const出现在*号左边时指针指向的数据为常量
- 当const出现在*后右边时指针本身为常量
(2)实践: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;
}
4、小结
- 指针是C语言中一种特别的变量
- 指针所保存的值是内存的地址
- 可以通过指针修改内存中的任意地址内容
二、数组的本质分析
1、数组的概念
- 数组是相同类型的变量的有序集合
2、数组的大小
(1)数组大小的获取(内存、元素数量)
- 数组在一片连续的内存空间中存储元素
- 数组元素的个数可以显示或隐式指定
(2)实践:数组的初始化
#include <stdio.h>
int main()
{
int a[5] = { 1, 2 };
int b[] = { 1, 2 };
printf("a[2] = %d\n", a[2]); // 默认为零
printf("a[3] = %d\n", a[3]);
printf("a[4] = %d\n", a[4]);
printf("sizeof(a) = %d\n", sizeof(a)); // 整个数组的所占内存大小 5 * 4 = 20byte
printf("sizeof(b) = %d\n", sizeof(b)); // 整个数组的所占内存大小 2 * 4 = 8byte
printf("count for a: %d\n", sizeof(a) / sizeof(int));// 数组所包含的元素数量 20 / 4 = 5
printf("count for b: %d\n", sizeof(b) / sizeof(*b)); // 数组所包含的元素数量 8 / 4 = 2
return 0;
}
3、数组地址与数组名
(1)数组地址与数组名
- 数组名代表数组首元素的地址
- 数组的地址需要用取地址符&才能得到
- 数组首元素的地址值与数组的地址值相同
- 数组首元素的地址与数组的地址是两个不同的概念
(2)实践:数组名和数组地址
#include <stdio.h>
int main()
{
int a[5] = { 0 }; // 将所有元素赋值为 0
printf("a = %p\n", a); // 数组名 -- 数组首元素的地址
printf("&a = %p\n", &a); // 取数组的地址
printf("&a[0] = %p\n", &a[0]); // 数组首元素的地址
return 0;
}
4、数组名的盲点
(1)数组名的易错点
- 数组名可以看做一个指针常量(无法改变 )
- 数组名“指向”的是内存中数组首元素的起始位置
- 数组名不包含数组的长度信息
- 在表达式中数组名只能作为右值使用(因为无法改变 )
- 只有在下列场合中数组名不能看做指针常量
- 数组名作为sizeof操作符的参数 取的是数组所占得内存
- 数组名作为&运算符的参数 取的是数组的地址
(2)实践:数组和指针并不相同
#include <stdio.h>
int main()
{
int a[5] = { 0 };
int b[2];
int* p = NULL;
p = a; // 将数组的首元素地址 赋值给 p
printf("a = %p\n", a); // 打印数组的首元素地址
printf("p = %p\n", p); // 打印数组的首元素地址
printf("&p = %p\n", &p);// 打印指针的地址
printf("sizeof(a) = %d\n", sizeof(a)); //打印数组所占内存 20byte
printf("sizeof(p) = %d\n", sizeof(*p));//打印数组首元素所占内存 4byte
printf("sizeof(p) = %d\n", sizeof(*(p + 1)));//打印数第二个元素所占内存 4byte
printf("sizeof(p) = %d\n", sizeof(p)); //打印指针所占内存 4byte
printf("\n");
p = b; // 将数组的首元素地址 赋值给 p
printf("b = %p\n", b); // 打印数组的首元素地址
printf("p = %p\n", p); // 打印数组的首元素地址
printf("&p = %p\n", &p); // 打印指针的地址
printf("sizeof(b) = %d\n", sizeof(b)); // 打印数组所占内存 8byte
printf("sizeof(p) = %d\n", sizeof(p)); // 打印指针所占内存 4byte
// b = a; 数组间不可直接赋值
return 0;
}
5、小结
- 数组是一片连续的内存空间
- 数组的地址和数组首元素的地址意义不同
- 数组名在大多数情况下被当成指针常量处理
- 数组名其实并不是指针,不能将其等同于指针
三、指针和数组分析(上)
1、数组的本质
(1)什么是数组?
- 数组是一段连续的内存空间
- 数组的空间大小为sizeof(array_type)* array_size 数组元素的类型大小 x 元素的数量
- 数组名可看做指向数组第一个元素的常量指针
(2)实践:a + 1的结果是什么?
#include <stdio.h>
int main()
{
int a[5] = {0};
int* p = NULL;
printf("a = 0x%X\n", (unsigned int)(a)); // 数组名地址
printf("a + 1 = 0x%X\n", (unsigned int)(a + 1)); // 数组名地址 + n * 数据类型大小
printf("p = 0x%X\n", (unsigned int)(p)); // 数组名地址
printf("p + 1 = 0x%X\n", (unsigned int)(p + 1)); // 数组名地址 + n * 数据类型大小
return 0;
}
2、指针的运算
- 指针是一种特殊的变量,与整数的运算规则为
结论∶
当指针p指向一个同类型的数组的元素时:p+1将指向当前元素的下一个元素;p-1将指向当前元素的上一个元素。
- 指针之间只支持减法运算
- 参与减法运算的指针类型必须相同
注意∶
1、 只有当两个指针指向同一个数组中的元素时,指针相减才有意义,其意义为指针所指元素的下标差
2、 当两个指针指向的元素不在同一个数组中时,结果未定义
3、指针的比较
(1)指针的比较
- 指针也可以进行关系运算(<,<=,>,>=)
- 指针关系运算的前提是同时指向同一个数组中的元素
- 任意两个指针之间的比较运算( ==,!= )无限制
- 参与比较运算的指针类型必须相同
(2)实践:指针运算初探
- 指针关系运算的前提是同时指向同一个数组中的元素
- 指针运算没有乘除
#include <stdio.h>
int main()
{
char s1[] = { 'H', 'e', 'l', 'l', 'o' };
int i = 0;
char s2[] = { 'W', 'o', 'r', 'l', 'd' };
char* p0 = s1; // 数组s1首元素地址赋值给p0
char* p1 = &s1[3]; // 数组s1第四个元素地址赋值给p1
char* p2 = s2; // 数组s2首元素地址赋值给p2
int* p = &i; // 变量i的地址赋值给p
printf("%d\n", p0 - p1); // -3
// printf("%d\n", p0 + p2); // ERROR 不指向同一个数组没意义
// printf("%d\n", p0 - p2); // ERROR 不指向同一个数组没意义
// printf("%d\n", p0 - p); // ERROR 不指向同一个数组没意义
// printf("%d\n", p0 * p2); // ERROR
// printf("%d\n", p0 / p2); // ERROR
return 0;
}
(3)实践:指针运算的应用
#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); // 数组的最后一个元素的地址+sizeof(*s)
char* p = NULL;
printf("pBegin = %p\n", pBegin); // 数组的首元素地址
printf("pEnd = %p\n", pEnd); // 数组的最后一个元素的地址+sizeof(*s)
printf("Size: %d\n", pEnd - pBegin);// 5 - 0 = 5
for (p = pBegin; p < pEnd; p++) // 打印每个字符
{
printf