指针
在计算机科学中,指针( Pointer )是编程语言中的一个对象,利用地址,它的值直接指向( points to )存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为"“指针"。意思是通过它能找到以它为地址的内存单元。 指针就是变量,用来存放地址的变量(存放在指针中的值都被当成地址处理)。
- 一个小的内存单元到底有多大?(一个字节)
- 如何编址?
一个字节给一个对应的地址是比较合适的。
对于32位的机器,假设有32根地址线那么假设每根地址线在寻址的产生的是一个电信号正点/负电(1或者0)
那么32根地址线产生的地址就会是:
00000000 00000000 00000000 00000000
... ...
11111111 11111111 11111111 11111111
总结:
- 指针是用来存放地址的,地址是唯一标示一块地址空间的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针类型的意义
指针类型不能随便写是有意义的。
- 指针类型决定了:指针解引用的权限有多大
- 指针类型决定了指针,走一步能走多远(步长)
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 10;
/*
* 第一种情况
因为 解引用是 int 类型 内存中就是 00000000
*/
int b = 0x55667799;
char* po = &b;
*po = 5;
/*
第二种情况
解引用是 char 类型 因为 char 是一个字节
内存中为 05 77 66 55 值改变了一个字节
*/
return 0;
}
// -----------------------------------分割线-----------------------------------
int main()
{
int arr[10] = { 0 };
int* p = &arr;
char* pc = &arr;
printf("%p \n", p);// 000000BFCF75F998
printf("%p \n", pc);// 000000BFCF75F998
printf("%p \n", p + 1);// 00000016EC4FF57C
printf("%p \n", pc + 1);// 00000016EC4FF579
/*
一开始存储的地址相同,但是进行计算因为 p 是 int 类型 加 4个字节
pc 是 char 类型 加一个字节 跳过其他。
*/
return 0;
}
// -----------------------------------分割线-----------------------------------
int main()
{
int arr[10] = { 0 };
int* pa = &arr;
for (int i = 0;10 > i;i++)
{
*(pa + i) = i+1;
}
// pa + i 其实是下标为 i 的地址
// 1 2 3 4 5 6 7 8 9 10
// 如果想当成一个整型来访问就使用int类型指针
// 操作一个字节一个字节 的访问就使用 char类型
// char 01 01 01 01 他会访问每一个字节并改变
return 0;
}
- int 指针 +1 跳过一个整型,char 指针 +1 跳过一个字符,数组指针 +1 跳过一个数组
野指针
- 概念︰野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
int main()
{
// 这里的 p 就是一个野指针
int *p;// p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
*p = 20;// 非法访问内存
return 0;
}
// ---------------------------------------------------------------------------------
// 越界访问
int main()
{
int arr[10] = { 0 };
int* p = arr;
// 0 - 10 包括 10 ,一共 11 个元素,数组本身10个元素
for (int i = 0; 10 >= i; i++)
{
// 最后一个元素就会 越界访问
*p = i;
p++;
}
return 0;
}
// ------------------------------------------------------------------
// 指针访问的空间 被释放了
int* text()
{
int a = 10;
return &a;// 当这个函数执行完毕后 a变量 的生命周期就结束了
} // 返回的只是一个地址
int main()
{
int* p = text();// 解 出一个地址
*p = 20; // 这个时候这个地址里面已经没有 10了 是一个不存在的
return 0;
}
如何避免野指针
- 指针初始化
- 当前不知道应该初始化什么地址的时候,直接初始化NULL
- 明确知道初始化的值
- 小心越界(c语言本身不会检查数据的越界行为的)
- 指针指向空间释放及时置为NULL
- 指针使用之前检查有效性(指针为NULL时是不能用的)
指针的运算
- 指针 + - 整数
- 指针 - 指针
- 指针的关系运算
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int* pen = arr + 9;
while (p <= pen)
{
printf("%d \n", *p);
// 1 2 3 4 5 6 7 8 9 10
p++;
}
return 0;
}
// -------------------------------------------------------------------------
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d", &arr[9] - &arr[0]);// 9
// 指针减去指针 得到的两个指针之间元素的个数
return 0;
}
- 指针和指针相减的前提是 指针指向同一块空间
int my_strlen(char * str)
{
char* stau = str;
while (*str != '\0')
{
str++;
}
// 指针 减 指针
return str - stau;
}
int main()
{
char arr[] = "abc";
// 求字符串长度
int i = my_strlen(arr);
printf("%d", i);
return 0;
}
指针和数组
- 数组名是数组首元素的地址
int main()
{
int arr[10] = { 0 };
// arr 数组名 是数组首元素的地址
printf("%p", arr);// 0000003D9032FC98
printf("%p", &arr[0]);// 0000003D9032FC98
return 0;
}
// -----------------------------------------------------------------
int main()
{
int arr[10] = { 0 };
int* pa = arr;
for (int i = 0; 10 > i; i++)
{
printf("%p <===> %p \n", &arr[i], pa + i);
// &arr[i] == pa+i
//*(pa + i) = 10;
}
return 0;
}
数组名
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
// [] 是一个操作符 2和arr是两个操作数
// a+b == b+a
printf("%d \n", arr[2]);// 3 arr[2] ==> *(arr+2)
printf("%d \n", 2[arr]); // 3
printf("%d \n", p[2]); // 3 p[2] ==> *(p+2)
printf("%d \n", 2[p]); // 3
/*
arr[2] <==> *(arr+2) <==> *(p+2) <==> *(2+p) <==> *(2+arr) == 2[arr]
2[arr] <==> *(2+arr)
*/
return 0;
}
二级指针
指针数组
指针数组是指针还是数组?
答案:是数组。是存放指针的数组
int main()
{
int arr[10];// 整型数组 - 存放整型的数组
char arr1[10];// 字符数组 - 存放字符的数组
// 指针数组 - 存放指针的数组
int* par[5];// 整型指针的数组
return 0;
}
结构体初阶
结构是一些值的集合,但是值的类型可以不同。
结构的成员可以是 变量、数组、指针、甚至是其他结构体。
struct B
{
char name[20];
int id;
};
struct Stu
{
// 结构体的成员是结构体
struct B bt;
// 成员变量
char name[20];// 名字
int age;
char id[10];
}s1,s2;// 也是结构体变量
// s1,s2 全局变量
// 要是在 main函数中创建 就是局部变量
void print1(struct Stu t)
{
// 这个 t 就是传递进来的参数
printf("%d %s \n", t.age, t.name);// 16 张三
}
void print2(struct Stu * z)
{
printf("%d %s", z->age, z->name);// 16 张三
}
int main()
{
// 初始化
struct Stu b = { {"李四",1652},"张三",16,"0112"};
// 访问 . ->
printf("%d \n", b.age);
printf("%s \n", b.id);
printf("%s \n", b.bt.name);
// 我们总会拿到 struct 中成员的地址
struct Stu* ps = &b;
printf("%d \n", (*ps).age);// 16
// 但是我们还可以写成
printf("%d \n", ps->age);// 16
print1(b); // 传值调用
print2(&b);// 传址调用 尽量选择这种 效率高 不会临时拷贝
return 0;
}
结论:结构体传参的时候,要传结构体的地址。