代码运行环境
本节教程所有代码都是在win11系统,Qt 5.12.12版本,mingw编译工具环境编写运行。创建的工程类型为Qt Console Application。
1.什么是指针
要理解指针,首先得知道程序具体执行的流程。程序是由高级语言如c、c++、java等开发的,经过编译器转译成机器码供cpu调度执行。cpu从内存中获取执行程序所需的数据。那这些数据存在内存中,cpu则是通过内存地址去获取。内存存取数据是这样的:内存单元按顺序排列,每个单元都有一个地址编号,cpu根据程序指令的要求从内存中按照指令给出的地址存取其中的数据。由此我们可以很容易分析出,对于系统内存,它既可以存实实在在的数据变量,也可以存其它内存的地址。我们通常把内存地址的这种变量称为指针。
- 指针有很多种类型。例如: int n; int* p = &n;这里指针p为整型,char* p; p为字符类型。
- 指针有个非常重要的功能:间接访问被指对象。
- 指针变量的占据内存的大小是固定的,若是32位的系统,指针是4字节,64位则是8字节。
#include <QCoreApplication>
#include <stdio.h>
#include <stdlib.h> //申明malloc函数的头文件
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int x = 258;
int*p =&x; //将指针指向变量x,这是指向的栈区内存
printf("%p\n",&x);
printf("存放指针的内存地址%p\n",&p); //打印指针变量本身的地址(存放指针的内存地址)
printf("指针变量的内容%p\n",p); //打印指针指向的地址(指针变量的内容)
p = (int*)malloc(sizeof(int));
printf("%p\n",&p); //将指针指向堆区内存。堆内存需要程序员自己手动分配。
return a.exec();
}
2.&和*运算符
- "&"是取地址运算符,用于取一个对象的地址。
- "*"是间接寻址运算符,用于访问指针所指向的对象。
#include <QCoreApplication>
#include <stdio.h>
#include <stdlib.h> //申明malloc函数的头文件
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int x = 258;
int* p; //声明一个指针
p = &var; //通过&取出var变量的地址并复制给指针p
int temp = *ptr; //通过*运算符获取指针p所指向的对象并获取到对象内容赋值给temp
// temp其实就等于var
return a.exec();
}
“&”运算符和"*"是一对互逆运算符 | ||
&*p 等价于: | &var | p |
*&var 等价于: | *p | var |
3. 指针与函数参数
函数参数有两类,一种是实参,一种是形参。实参是真正传入函数的参数,形参是函数规定的传入参数的模版。在main函数调用swap函数时,编译器就会将实参的值复制一份给形参。形参相当于实参的一个副本。
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
printf("a= %d, b = %d\n", a, b);
}
main函数中调用swap函数,传入了真实的数据。x,y,就是实参。
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int x = 2;
int y = 3;
swap(x,y);
printf("x= %d, y= %d\n", x, y);
return a.exec();
}
3.1 值传递
在值传递中,形参与实参是存放在两个位置。main函数调用swap函数,将x,y两个是参传入swap函数,swap函数将实参x,y的值复制给了形参a,b。swap函数调换的实际上是实参x,y的副本,对x,y本身没有做任何操作。最终x,y的值并没有调换。
#include <QCoreApplication>
#include <stdio.h>
void swap(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
printf("a= %d, b = %d\n", a, b);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int x = 2;
int y = 3;
swap(x,y);
printf("x= %d, y= %d\n", x, y);
return a.exec();
}
3.2 指针传递
指针传递则是在swap中将形参指定为指针,这样的作用是获取实参的地址。可以这样理解,实参是main函数中x,y的地址,在调用swap的时候复制了这两个地址给px,py。针对指定内存地址的修改能改变x,y的值。
#include <QCoreApplication>
#include <stdio.h>
void swap(int* px, int* py)
{
int tmp = *px;
*px = *py;
*py = tmp;
printf("px= %d, b = %d\n", *px, *py);
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int x = 2;
int y = 3;
swap(&x,&y);
printf("x= %d, y= %d\n", x, y);
return a.exec();
}
4. 空指针
空指针一般是全局变量,提前就被定义好的。在 /usr/include/linux/stddef.h 这个文件中可以查看到。
- 空指针NULL表示不指向任何东西。NULL实际上是用(void*)0表示,指向0的指针。
- p = NULL等价于 p = 0。
- void*为通用指针,可以把任何类型的地址赋值给通用指针。
- 不能对空指针解引用。(*)
5. 通用指针
void*为通用指针,可以把任何类型的地址赋值给通用指针。
int n = 100
void* gp = &n; //将整型指针赋值给一个通用指针,指针所指向的地址不会改变,但是指针的类型信息会丢失。
//int nn = *gp; 这样写出现错误,因为gp指针丢失了指向的变量的类型,无法知道是取多少字节的内存赋值给nn
int* p = (int*)gp; //将通用型指针强转为int型指针
double* p2 = (double*)gp; //语法正确,但存在语义错误,编译器无法识别
6. 指针算术运算符
指针本身所占用的内存空间是固定的,那为什么要给指针设置不同的类型呢,int型指针和char型指针的在64位机器上都是占8字节,为什么不统一成一种,就是因为有指针运算这件事。不同类型的指针
指针只能进行加法与减法运算,有这些运算符可以使用:+、 -、 ++、 --、+=、-=。
指针运算的两种形式:指针 (运算符)整数,或者 指针 - 指针
- 指针与整数加减的结果是一个新的指针,这个新的指针指向的是原来指针在下移或上移整数个存储单元之后的内存地址。存储单元的大小是根据该指针的数据类型所需的内存大小决定的。
#include <QCoreApplication>
#include <stdio.h>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
int n = 100;
int* p = &n;
printf("%p", p);
printf("%p", p+1); //在n地址上下移4字节
printf("%p", &n+2); //在n地址上下移2个字节
return a.exec();
}
- 指针与指针的减运算的结果是一个整数。这个整数表示的是两个指针之间的数据的个数,并非两个地址的差值