什么是指针
C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。这里,数据对象是指存储在内存中的一个指定数据类型的数值或字符串,它们都有一个自己的地址,而指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量。前面已经提到内存其实就是一组有序字节组成的数组,数组中每个字节大大小固定,都是 8bit。对这些连续的字节从 0 开始进行编号,每个字节都有唯一的一个编号,这个编号就是内存地址。示意如下图:
这是一个 4GB 的内存,可以存放 2^32 个字节的数据。左侧的连续的十六进制编号就是内存地址,每个内存地址对应一个字节的内存空间。而指针变量保存的就是这个编号,也即内存地址。
怎么获得变量的地址
前面我们学习过scanf函数,在输入数据时用到了&符号,这个符号就是获取变量的地址的符号,当然一些特殊数据的地址表示方式也不一样,C语言常用获取地址主要有以下三种:
- 普通变量地址: &变量名
- 一维数组首地址: 数组名或者&数组名
- 函数地址: 函数名或者&函数名
如下测试代码:
#include <stdio.h> int Max(int a, int b) { return a > b ? a : b; } int main() { int inum = 1; int array = { 1,2,3 }; printf("&inum=%p\n", &inum); printf("array=%p\t&array=%p\n", array, &array); printf("Max=%p\t&Max=%p\n", Max, &Max); return 0; }
注意的程序运行占用的内存是虚拟内存,每次程序运行得到的结果都不一样,如下运行结果:
为什么要使用指针
在C语言中,指针的使用非常广泛,因为使用指针往往可以生成更高效、更紧凑的代码。总的来说,使用指针有如下好处:
- 指针的使用使得不同区域的代码可以轻易的共享内存数据,这样可以使程序更为快速高效;
- C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等;
- C语言是传值调用,而有些操作传值调用是无法完成的,如通过被调函数修改调用函数的对象,但是这种操作可以由指针来完成,而且并不违背传值调用。
如何声明一个指针
基本数据类型的指针创建非常简单,只需要使用: 类型* 标识符;即可产生一个指针,如下代码:
#include <stdio.h> int main() { int* p1; char* p2; double* p3; return 0; }
指针的类型
从语法的角度看,你只要把 指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:
- int*ptr:指针的类型是int*
- char*ptr:指针的类型是char*
- int**ptr:指针的类型是int**
- int(*ptr):指针的类型是int(*)
- int*(*ptr):指针的类型是int*(*)
怎么样?找出指针的类型的方法是不是很简单?
指针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。从语法上看,你只须 把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:
- int*ptr:指针所指向的类型是int
- char*ptr:指针所指向的的类型是char
- int**ptr:指针所指向的的类型是int*
- int(*ptr):指针所指向的的类型是int()
- int*(*ptr):指针所指向的的类型是int*()
指针的类型(即指针本身的类型)和指针所指向的类型(即指针操作的内存中数据的类型)是两个概念。当你对C语言越来越熟悉时,你会发现,把与指针搅和在一起的”类型”这个概念分成”指针的类型”和”指针所指向的类型”两个概念,是精通指针的关键点之一。并且在指针的运算中,这两种类型的保持一致尤为重要。以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?
指针的特殊状态
野指针
野指针(wild pointer)就是没有被初始化过的指针,指针指向的位置是不可知的(随机的、不正确的、没有明确限制的),如果用visual studio 2022编译,会直接warning C4700: 使用了未初始化的局部变量“p”,强行运行还会引发中断提示,还是比较人性化的,如下代码运行结果:
空指针
空指针就是被赋值为NULL的指针,它不指向任何的对象或者函数。空指针的出现是为了避免错误的引用指针而导致的难以排查的问题,不过空指针也不能直接访问,但是可以用来判断。注意是
不能访问空指针的数据,否则程序就会崩,如下代码运行结果:
悬浮指针
悬浮指针是指针最初指向的内存已经被释放了的一种指针。避免悬浮指针的一个方法是赋值为NULL,尤其是后续动态内存申请的时候,释放内存一定要记得置空处理
指针的运算
取值运算
指针的取值运算有以下两种:
- *指针变量
- 指针变量
如下测试代码:
#include <stdio.h> int main() { int data = 666; //*靠近int和p都等效 int* p = &data; printf("p=%d\t*p=%d\n", p, *p); //*p等效p等效data *p = 777; printf("p=%d\t*p=%d\tdata=%d\n", p, *p,data); return 0; }
运行结果如下:
算术运算
指针+n 或者指针-n
可以对指针变量 p 进行 p++、p–、p + i 等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i 个操作数。如下图:
在上图中,10000000等是内存地址的十六进制表示(数值是假定的),p 是一个 int 类型的指针,指向内存地址 0x10000008 处。则 p+1将指向与 p 相邻的下一个内存地址,由于 int 型数据占 4 个字节,因此 p++ 所指的内存地址为 1000000b。其余类推。不过要注意的是,p+n或p-n这种运算并不会改变指针变量 p自身,p++或者p–则会改变自身的值。如下测试代码:
#include <stdio.h> int main() { char* p1 = NULL; int* p2 = NULL; int (*p3) = NULL; printf("p1=%p\tp2=%p\tp3=%p\n", p1, p2, p3); printf("p1+1=%p\tp2+1=%p\tp3+1=%p\n", p1+1, p2+1, p3+1); return 0; }
运行结果如下:
上图中,p1是char*类型,操作char数据,偏移一个操作数,故移动一个1字节,p2是int* 类型 操作int数据,移一个操作数,故移动一个4字节,p3是int 类型 操作长度为3的数组,移一个数组长度,故移动一个12字节,C是12的十六进制。 综上,p+n 偏移字节数是 p+sizeof(指针所指向的类型)*n。
指针 – 指针
只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。两个指针相减的结果的类型是 ptrdiff_t(long),它是一种有符号整数类型。减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。 如下测试代码:
#include <stdio.h> int main() { int array = { 1,2,3,4,5,6,7,8,9,0 }; int* p1 = &array; int* p2 = &array; printf("%lld\n", p2 - p1); return 0; }
运行结果如下:
void*指针
void* 类型的指针又叫万能指针,他可以指向任意的内存空间地址,可以隐式自动转换为其他类型指针,不能对void*直接取值操作,因为它没有类型,或者说不能判断存储的是什么类型,需要强转指定一个确定的类型才能使用。如下测试代码:
#include <stdio.h> int main() { int num = 0; void* p = 34;%d\n", *p); printf("%d\n", ((int*)p)); printf("%d\n", *((int*)p)); void* pstr = "ILoveyou"; //隐式转换 char* str = pstr; puts(str); return 0; }
const与指针
const是constant的简写,只要一个变量前面用const来修饰,就意味着该变量里的数据可以被访问,不能被修改。也就是说const意味着“只读”。任何修改该变量的尝试都会导致编译错误。const是通过编译器在编译的时候执行检查来确保实现的(也就是说const类型的变量不能改是编译错误,不是运行时错误。)所以我们只要想办法骗过编译器,就可以修改const定义的常量,而运行时不会报错。
const与指针可以搭配出三种不同的含义:
指针指向的内存不可修改,但指针的指向可以修改
#include <stdio.h> int main() { int age = 18; const int* ptr = &age; //常量指针 int const* ptr = &age; //和上面一句是等价的 //err,不能修改 //*ptr = 20; ptr = NULL; //ok,可以修改指向 return 0; }
指针的指向不可以修改,但指向的内存可以修改
#include <stdio.h> int main() { int age = 123; int* const ptr = &age; //ok *ptr = 20; //错误,ptr不能被修改 //ptr = NULL; return 0; }
指针的指向不可以修改,指向的内存也不可以修改
#include <stdio.h> int main() { int age = 123; const int* const ptr = &age; //所指向的内容不可改变 //*ptr = 20; //指针变量不可改变 //ptr = NULL; return 0; }
常量指针(指向常量的指针)是指指向常量的指针,顾名思义,就是指针指向的是常量,即,它不能指向变量,它指向的内容不能被改变,不能通过指针来修改它指向的内容,但是指针自身不是常量,它自身的值可以改变,从而指向另一个常量。
指针常量(指针是常量)是指指针本身是常量。它指向的地址是不可改变的,但地址里的内容可以通过指针改变。有一点需要注意的是,指针常量在定义时必须同时赋初值。
客观请留步
如果阁下正好在学习C/C++,看文章比较无聊,不妨关注下关注下小编的视频教程,通俗易懂,深入浅出,一个视频只讲一个知识点。视频不深奥,不需要钻研,在公交、在地铁、在厕所都可以观看,随时随地涨姿势。
本文【c语言int类型占几个字节(int到底占几个字节)】由作者: C/S结构 提供,本站不拥有所有权,只提供储存服务,如有侵权,联系删除!
本文链接:https://www.cuoshuo.com/blog/4558.html