hello~友友们好,我是林林。最近在学习指针,还真是有些头疼啊。下面是我整理的一些学习笔记,我们一起来学习吧。
目录
一、内存和编址
(1)内存分为一个个的内存单元,每个内存单元的大小取一个字节(也就是能够存放二进制中的一个0/1)。每个内存单元有一个编号,即地址。在C语言中,我们将地址叫做指针.
(2)8 bit = 1 byte 此后的内存单位从小到大依次是:KB MB GB TB PB 相邻两个单位之间的进制为1024(2 ^ 10)。
(3)在CPU和内存之间有一组线,叫做地址总线。对于32(或64)位机器,我们可以理解为一共有32(或64)根地址总线,每一根线有两态(0或1),那么32(或64)根线就有2^ 32(或2 ^ 64)种含义,每一种含义代表一个地址。
(4)地址信息被下达给内存后,在内存上可以找到地址对应的数据,通过数据总线可以将数据传入CPU寄存器。
二、指针变量
(1)C语言中创建变量其实是向内存申请空间。比如我们创建一个整型变量a,内存会申请4个字节存放。而& 取出的是4个字节中较小的字节地址,不是全部的地址。
#include <stdio.h>
int main()
{
int a = 10;
//%p是专门用来打印地址的,显示的是16进制
//%x,%X 也是16进制,只不过会将前面的0省略
printf("%p\n",&a);
return 0;
}
(2)我们取出的地址需要放到指针变量中,接下来我们来创建指针变量。
#include <stdio.h>
int main()
{
int a = 0;
int * p = &a;
//这里的p就是指针变量
return 0;
}
我们看到指针的类型为int * ,那么如何理解呢?
* 代表变量p 为指针变量 ; 而int 是意为 指针p 所指向的对象的类型是 int 类型。
POINT : 这里我们要清楚: p 为指针变量,指针==地址 ; *p == a 。
(3)指针变量的大小
在32(或64)位环境下,指针变量的大小为4(或8)个字节;
(因为:1字节=8 比特;32 /8 = 4; 64 / 8 = 8。)
指针变量的大小与其类型无关,在相同的环境下相同。
三、 解引用
(1)* 就是解引用操作符
#include <stdio.h>
int main()
{
int a = 10;
int * p = &a;
//*p 的意思是通过p存放的地址,找到p指向的对象。*p == a
//此操作为解引用,将a的值更改
* p = 20;
return 0;
}
(2)指针的类型决定了其解引用时的权限(一次能操作几个字节)
char* 的指针解引⽤就只能访问⼀个字节,⽽ int* 的指针的解引⽤就能访问四个字节。
(3)指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)
char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。
四、void*指针
(1)⽆具体类型的指针(或者叫泛型指 针),这种类型的指针可以⽤来接受任意类型地址。
(2)局限性:不能直接进行指针的+-整数和解引⽤的运算。
(3)void* 类型的指针是使⽤在函数参数的部分,⽤来接收不同类型数据的地址,使得⼀个函数来处理多种类型的数据。
五、const 修饰指针
(1)const修饰变量后,变量无法再被修改。
#include <stdio.h>
int main()
{
int a = 10;
a = 20; //可以被修改
const int b = 10;
b = 20; //不可以被修改
return 0;
}
这里的b虽然无法被修改,但b仍然是变量,只是在语法上加了限制。
但如果我们通过修改b的地址,就可以达到目的。
#include <stdio.h>
int main()
{
const int b = 0;
int * p = &b;
*p = 10; //可以将b修改
return 0;
}
如果让p拿到b的地址也不能修改b,怎么做呢?
(2)const 修饰指针变量
(1)int const *p = &b;
const如果放在*的左边,修饰的是指针指向的内容(*p 或 b),保证指针指向的内容(b)不能通过指针来改变;但是指针变量本⾝的内容(p指向谁)可变。
(2)int *const p = &b;
const如果放在*的右边,修饰的是指针变量本⾝(p),保证了指针变量的内容(p指向谁)不能修改,但是指针指向的内容(*p 或 b),可以通过指针改变。
(3)int const * const p = &b;
两者都不能改变。
六、指针运算
(1)指针+-整数
#include <stdio.h>
//指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//p存放数组第一个元素的地址
int *p = &arr[0];
int i = 0;
//计算数组的长度
int sz = sizeof(arr)/sizeof(arr[0]);
//用循环打印数组的各个元素
for(i=0; i<sz; i++)
{
printf("%d ",*(p+i));//p+i这⾥就是指针+整数
}
return 0;
}
(2)指针-指针
//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
//将字符串首元素的地址存放于p中
char *p = s;
while(*p != '\0' )
p++;
//指针-指针:计算字符串的长度
return p-s;
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
注意:没有 指针+指针 这一说。
(3)指针的关系运算
//指针的关系运算
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
//为什么不是<= ? 因为数组的下标是从0开始的
while(p<arr+sz) //指针的⼤⼩⽐较
{
printf("%d ", *p);
p++;
}
return 0;
}
七、野指针
(1)定义
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。
(2)野指针成因:
1.指针未初始化;
2.指针越界访问;
3.指针指向的空间释放:
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
(3)如何规避野指针:
1.指针初始化
#include <stdio.h>
int main()
{
int a = 0;
int * p = &a;
//NULL是指针类型,因此是p而不是*p
p = NULL;
int * q = NULL;
return 0;
}
2. ⼩⼼指针越界
3.指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性
if (p != NULL)
{
...
}
4.避免返回局部变量的地址
对应(2)3.
八、assert断言
(1)头文件:#include <assert.h>;
(2)确保程序符合指定条件,如果不符合,就报错终⽌运⾏;
assert(p != NULL);
(3)好处
1.不仅能⾃动标识⽂件和出问题的⾏号,还有⼀种⽆需更改代码就能开启或关闭 assert() 的机制。如果已经确认程序没有问 题,不需要再做断⾔,就在 #include 语句的前⾯,定义⼀个NDEBUG 。
#define NDEBUG
#include <assert.h>
2.在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert。在 Release 版本不影响用户使⽤时程序的效率(优化掉了)。
(4)缺点
增加了程序的运行时间。
九、传值调用和传址调用
(1)实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实 参。。所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采⽤传值调⽤。
(2)传址调⽤,可以让函数和主调函数之间建⽴真正的联系,在函数内部可以修改主调函数中的变量;如果函数内部要修改主调函数中的变量的值,就需要传址调⽤。
十、写在最后
友友们~我们一起努力,拿下指针!!