C语言学习!
目录
文章目录
前言
一、指针是什么?
二、指针变量的大小
三、指针和指针类型
四、指针和函数
五、野指针
5.1野指针成因
5.2 如何规避野指针
六、指针运算
6.1 指针+- 整数
6.2 指针-指针
6.3 指针的关系运算
总结
前言
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址。
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。
一、指针是什么?
1.1 指针变量
整个内存空间是切割成一个个1byte(字节)大小的内存单元并编号来管理的,把内存单元的编号称为地址,地址也叫指针。存放指针(地址)的变量就是指针变量,通过指针变量里存放的地址,可以找到对应的内存单元。
就好比一个人国家划分成许多省,每个省都有邮政编码,根据邮政编码可以找到对应地址的省,而指针变量就像是记录邮政编码的小本子。
- 内存单元有编号,而这个编号其实就是地址,地址被称为指针。
- 存放指针(地址)的变量就是指针变量。
- 把内存单元的编号就称为指针。
- 指针其实就是地址,地址就是编号。
- 指针就是内存单元的编号。
总结:指针就是地址,口语中说的指针通常指的是指针变量。
代码示例:
#include <stdio.h>
int main()
{
int a = 10;//a是整型变量,占用四个字节的内存空间,存储10
int* pa = &a;//pa是一个指针变量,用来存放地址的
return 0;
}
调试窗口:
a变量里存放着10,a的地址是0x008ffd80,&a的意思是取得a的地址,运算符&的功能就是取得对象的地址。pa变量中存放的是a的地址0x008ffd80,而pa前面的int表示p指向的对象是int类型的,*说明p是指针变量。
这里注意:int 整形变量占4个字节的内存,&a取出的是a所占4个字节的第一个字节的地址存放在pa变量中,pa指针变量中存的就是a的首地址。
总结: 指针变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
1.2 取地址运算符和间接寻址运算符
C语言为指针的使用提供了一对运算符。为了找到变量的地址,可以使用 & 取地址运算符。如果 a 是变量,那么 &a 就是 a 在内存中的地址。为了获得对指针所指向对象的访问,可以使用 * 间接寻址运算符。如果 p 是指针,那么 *p 表示 p 当前指向的对象。
只要 p 指向 a ,*p 就是 a 的别名。
代码示例:
#include <stdio.h>
int main()
{
int a = 0;
int* p = &a;
*p = 10;
return 0;
}
运行结果:
*为解引用操作符,*p意思就是通过p中存放的地址,找到p所指向的对象(此时*p就是a)
&a:找到a的地址;*p找回对象a。
二、指针变量的大小
指针的大小是由硬件机器决定的:
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以 一个指针变量的大小就应该是4个字节。
在64位的机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地 址。
总结:
- 指针变量是用来存放地址的,地址是唯一标示一个内存单元的。
- 指针的大小在32位平台是4个字节,在64位平台是8个字节。
代码示例:
#include <stdio.h>
int main()
{
char* pc = NULL;
short* ps = NULL;
int* pi = NULL;
double* pd = NULL;
printf("%zu\n", sizeof(pc));
printf("%zu\n", sizeof(ps));
printf("%zu\n", sizeof(pi));
printf("%zu\n", sizeof(pd));
return 0;
}
4
4
4
4
不管什么类型的指针都是在创建指针变量,指针变量是用来存放地址的。指针变量的大小取决于一个地址存放的时候需要多大空间。
三、指针和指针类型
指针的定义方式是: type + * 。
- char* 类型的指针是为了存放 char 类型变量的地址。
- short* 类型的指针是为了存放 short 类型变量的地址。
- int* 类型的指针是为了存放 int 类型变量的地址。
代码示例1:
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
*pa = 0;
return 0;
}
调试窗口:
代码示例2:
#include <stdio.h>
int main()
{
int a = 0x11223344;
char* pa = &a;
*pa = 0;
return 0;
}
调试窗口:
结论:
指针类型决定了指针在被解引用的时候访问几个字节。
- int*的指针,解引用访问4个字节
- char*的指针,解引用访问1个字节
代码示例:
#include <stdio.h>
int main()
{
int a = 0x11223344;
int* pa = &a;
char* pc = &a;
printf("pa=%p\n", pa);
printf("pa+1=%p\n", pa+1);
printf("pc=%p\n", pa);
printf("pc+1=%p\n", pc+1);
return 0;
}
运行结果:
pa=008FF774
pa+1=008FF778
pc=008FF774
pc+1=008FF775
结论:
- 指针类型决定了指针+1或-1操作的时候,跳过几个字节。
- 指针的类型决定了指针的步长。
代码示例1:
#include <stdio.h>
int main()
{
int a = 0;
int* pi = &a;
float* pf = &a;
*pi = 100;
return 0;
}
调试窗口:
代码示例2:
#include <stdio.h>
int main()
{
int a = 0;
int* pi = &a;
float* pf = &a;
*pf = 100.0;
return 0;
}
调试窗口:
int* 和 float* 不能通用
- pi 解引用访问4个字节,pi+1 也是跳过4个字节。
- pf 解引用访问4个字节,pf+1 也是跳过4个字节。
但整型数字和浮点型小数在内存中的存储方式不同,所以在监视内存中的结果也不同,也论证 int* 和 float* 不能通用。
四、指针和函数
指针的一个重要作用就是作为函数参数使用。
代码示例:
#include <stdio.h>
void text(int* y)
{
*y = 20;
}
int main()
{
int a = 0;
text(&a);
printf("a=%d", a);
return 0;
}
运行结果:
a=20
通过函数调用表达式 text(&a) ,调用函数 text 时, text 函数中形参 y 被声明为指向 int 型变量的指针变量。函数被调用时,将 &a 复制到 y 中,指针 y 便指向了a。
由于在指针前加上指针运算符 * ,就可以显示该指针指向的对象。因此 *y 时 a 的别名。对 *y 赋值,也就是对 a 赋值,所以即使从 text 函数返回 main 函数,a 中保存的依然是180。
若要在函数中修改变量的值,就需要传入指向该变量的指针,即告诉程序:传入的是指针,请对该指针指向的对象进行处理。
只要在被调用的函数里的指针前写上指针运算符 * ,就能间接地处理该指针指向的对象。这也是 * 运算符又被称为间接访问运算符的原因。另外,通过在指针前写上指针运算符 * 来访问该指针指向的对象,称为解引用。
这里可以对照函数的传址调用加深理解 C语言 函数-CSDN博客
计算和与乘积
代码示例:
#include <stdio.h>
void sum_mul(int x, int y, int* Sum, int* Mul)
{
*Sum = x + y;
*Mul = x * y;
}
int main()
{
int a = 3;
int b = 6;
int sum = 0;
int mul = 0;
sum_mul(a, b, &sum, &mul);
printf("sum=%d\n", sum);
printf("mul=%d\n", mul);
return 0;
}
运行结果:
sum=9
mul=18
调用函数 sum_mul 时,会将 sum 和 mul 的地址复制给形参 Sum 和 Mul 。因此 *Sum 就是 sum 的别名,*Mul 就是 mul 的别名。在函数体中,将求得的和赋值给 *Sum,将求得的乘积赋值给 *Mul 这就相当于给 sum 和 mul 进行赋值,因此从 sum_mul 函数返回到 main 函数之后,和与乘积也分别存储在 sum 和 mul 中了。
五、野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
5.1 野指针成因
1. 指针未初始化
代码示例1:
#include <stdio.h>
int main()
{
int* p;
*p = 10;
return 0;
}
- p没有初始化,就意味着没有明确指向。
- 一个局部变量不初始化时,放的是随机值:0xcccccccc
- *p = 10; 这里非法访问内存了,这里的p就是野指针。
2. 指针越界访问
代码示例2:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = &arr;
int i = 0;
for (i = 0; i <= 10; i++)
{
*p = i;
p++;
}
return 0;
}
当指针指向的范围超出数组arr的范围时,p就是野指针。
3. 指针指向的空间释放
代码示例3:
#include <stdio.h>
int* text()
{
int a = 10;
return &a;
}
int main()
{
int* p = text();
return 0;
}
调用text函数,返回变量a的地址给指针p。
a是局部变量,他所占的空间一旦出了text函数就销毁了,就是将a所占的空间还给了操作系统。所以返回地址的同时,a所占空间销毁。
p在存a的地址的时候,地址所对应的空间已经销毁。p虽然可以通过地址找到对应空间,但是p不能访问和使用这块空间。此时p就是野指针。
5.2 如何规避野指针
1. 指针初始化
2. 小心指针越界
3. 指针指向空间释放,及时置NULL
4. 避免返回局部变量的地址
5. 指针使用之前检查有效性
代码示例:
#include <stdio.h>
int main()
{
int* p = NULL;
int a = 10;
p = &a;
if (p != NULL)
{
*p = 100;
}
return 0;
}
六、指针运算
6.1 指针+- 整数
如果 p 指向数组元素 arr[ i ] ,那么 p+j 指向 arr[ i+j ]。
如果 p 指向数组元素 arr[ i ] ,那么 p-j 指向 arr[ i-j ]。
代码示例1:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0; i < sz; i++)
{
*p = 1;
p++;
}
return 0;
}
调试窗口:
代码示例2:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = arr;
for (i = 0; i < sz; i++)
{
*(p + i) = 1;
}
return 0;
}
调试窗口:
6.2 指针-指针
当两个指针相减时,结果为指针之间的距离,也就是两指针之间数组元素的个数。因此,若 p 指向 arr[ i ] 且 q 指向 arr[ j ],那么 p - q 就等于 i - j 。
代码示例:
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%d\n", &arr[9]-&arr[0]);
return 0;
}
运行结果:
9
- 指针-指针的绝对值得到的是指针和指针之间元素个数。
- 不是所有指针都能相减,指向同一块空间的2个指针才能相减。
代码示例:
#include <stdio.h>
int my_strlen(char* str)
{
char* start = str;
while (*str != '\0')
{
str++;
}
return (str - start);
}
int main()
{
int len = my_strlen("abcdefg");
printf("%d\n", len);
return 0;
}
运行结果:
7
6.3 指针的关系运算
可以用关系运算符( < 、<= 、> 、>= )和判等运算符(== 、!=)进行指针比较。
代码示例:
#include <stdio.h>
#define N_VALUES 10
int main()
{
int values[N_VALUES] = { 0 };
int* vp = NULL;
for (vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 1;
}
return 0;
}
总结
以上就是今天要讲的内容,本文仅仅简单介绍了指针。