深入浅出之指针
一、指针的概念
在程序中,我们所定义的变量,都要在内存中占有一个可标识的存储区域。每一个存储区域由若干个字节组成,在内存中每一个字节都有一个“地址”,一个存储区域的“地址”指的是存储区域中第一个字节的地址。
指针是编程语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”,意思是说通过它能找到以它为地址的内存单元。
指针并不是用来存储数据的,而是用来存储数据在内存中地址,它是内存数据的快捷方式,通过这个快捷方式,即使你不知道这个数据的变量名也可以操作它。
二、指针与指针变量
2.1 直接访问与间接访问
直接访问:通过变量名或地址访问程序中一个实体的存储空间方式(直接通过定义的变量来获取变量的数值)
间接访问:通过把地址存放在一个变量中,然后通过先找出地址变量中值,再由此地址找到最终要访问的变量的方法(通过指针的形式,指向原来变量存储的值)
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一个整形变量int num = 5;//! 定义一个指针int *p = #//! 直接访问形式,就是直接通过原来的变量名来进行访问qDebug("%d",num);//! 间接的访问形式,就是间接通过指针指向的变量,来间接的访问原来变量的,此处的*为指向的意思qDebug("%d",*p);return a.exec(); }输出为: 5 52.2 p、*p和&p的区别
p是一个指针变量的名字,表示此指针变量指向的内存地址。
*p表示此指针指向的内存地址中存放的内容,一般是一个和指针类型一致的变量或者常量。
&p就是取指针p的地址。
&p和p有什么区别?
区别在于,指针p同时也是个变量,既然是变量,编译器肯定要为其分配内存地址,就像程序中定义了一个int型的变量i,编译器要为其分配一块内存空间一 样。而&p就表示编译器为变量p分配的内存地址,而因为p是一个指针变量,这种特殊的身份注定了它要指向另外一个内存地址,程序员按照程序的需要 让它指向一个内存地址,这个它指向的内存地址就用p表示。而且,p指向的地址中的内容就用*p表示。
2.3 *和&运算
先理解地址和数据,想象内存里面是一个个的小盒子,每个盒子对应一个编号,这个编号就是地址,盒子里存放的就是数据。
&是取地址运算符,如有 int a; 即有一个小盒子里面存放的数据起名叫a,&a就是取a的地址,即该盒子的编号。
*(地址)是取值运算符,这里*是解引用操作符,可以理解成打开对应地址编号的盒子,取出里面的数据。*(&a) 就是打开a对应的小盒子,取出里面的数据,即*(&a)和a等价。
(*p)操作是这样一种运算,返回p 的值作为地址的那个空间的取值。
(&p)则是这样一种运算,返回当时声明p 时开辟的地址。
2.4 *&p和&*p区别
根据运算优先级,*&p 等价于*(&p)。&*p 等价于&(*p)。
1、如果p是int *指针变量,那么*&p = p,&*p = p,都是p。
2、如果p是一个int变量,那么*&p = p;而&*p是非法的,因为*p非法。
比如int p =10;那么*&p = *(&p) = p = 10(即从p的地址取值),而&*p = &(*p) 则非法,因为p=10,*10是取内存地址为10的值,这在c语言中是不合法的。
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一个变量int num = 5;//! 定义一个指针int *p;p = #// *p = 5;//! 定义个二级指针int **pp;pp = &p;//! 输出变量num的地址qDebug("p address =%x",*(&p));qDebug("p address =%x",&(*p));return a.exec(); } 输出: p address =6ff6f900 p address =6ff6f9002.5 指针与指针变量区别
系统为每一个内存单元分配一个地址值,C/C++把这个地址值称为“指针”。如有int i=5;,存放变量i的内存单元的编号(地址)&i被称为指针。
“指针变量”则是存放前述“地址值”的变量,也可以表述为,“指针变量”是存放变量所占内存空间“首地址”的变量(因为一个变量通常要占用连续的多个字节空间)。比如在int i=5;后有一句int *p=&i;,就把i的指针&i赋给了int *型指针变量p,也就是说p中存入着&i。所以说指针变量是存放指针的变量。
有一个事实值得注意,那就是有不少资料和教科书并没有如上区分,而是认为“指针是指针变量的简称”,如对int *p=&i;的解释是:声明一个int *型指针p,并用变量i的地址初始化;而严格说应该是声明一个int *型指针变量p才对。所以有时看书要根据上下文理解实质,而不能过于拘泥于文字表述。
2.6 一级指针与二级指针
我们定义一个指针变量int *p; p是指针变量,专门用来存放地址。
int *p=&a;相当于int *p; p=&a;
p存放的是a的地址,*p也等价于 a。指针变量p既然是变量,也同变量a一样对应一个小盒子,也有一个地址编号,&p就是取指针p的地址。这样就好理解二级指针了。
*p和**p的区别
int *p :一级指针,表示p所指向的地址里面存放的是一个int类型的值
int **p :二级指针,表示p所指向的地址里面存放的是一个指向int类型的指针(即p指向的地址里面存放的是一个指向int的一级指针)
例:
int a=5; //定义整形变量
int *p=&a; //定义一个指针指向这个变量
int **p1=&p; //定义一个二级指针指向p指针
以上3行输出的值都是5 。
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一个变量int num = 5;//! 定义一个指针int *p;p = #// *p = 5;//! 定义个二级指针int **pp;pp = &p;//! 输出变量num的地址qDebug("%x",**pp);return a.exec(); } 输出: 52.7 sizeof(p) 与 sizeof(*p)
对于任何指针变量p,变量p本身就是指针,它的大小就是指针的大小。p是p指向的,而*p的大小是被指向的大小。
sizeof(p)是指针本身的大小。这取决于地址总线的大小。这意味着对于64位系统,地址总线大小将是64位(8字节),因此指针将是8字节长(这表明您的系统是64位)。在32位系统上,它的大小是32位(4字节)。
如果sizeof(*p)返回的是p所指向的大小,那么您就知道int的大小(在您的例子中p指的是)是4字节,这在32位和64位系统上都是正常的。
例子1:
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一个变量int num = 5;//char num = 5;//! 定义一个指针int *p;p = #qDebug("size of int = %d",sizeof(int));qDebug("size of p = %d",sizeof(p));qDebug("size of *p = %d",sizeof(*p));return a.exec(); }输出: size of int = 4 size of p = 8 size of *p = 4例子2:
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一个变量//int num = 5;char num = 5;//! 定义一个指针char *p;p = #//! 输出变量num的地址qDebug("size of char = %d",sizeof(char));qDebug("size of p = %d",sizeof(p));qDebug("size of *p = %d",sizeof(*p));return a.exec(); } 输出: size of char = 1 size of p = 8 size of *p = 1例子3:
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一个数组int array[10]={1,2,3,4};//! 定义一个指针int *p = array;qDebug("sizeof of array = %d\n",sizeof(array)/sizeof(array[0]));qDebug("sizeof of array = %d\n",sizeof(p)/sizeof(*p));return a.exec(); } 输出: sizeof of array = 10sizeof of array = 22.8 指针初始化
对指针进行初始化或赋值只能使用以下四种类型的值
:
1. 0 值常量表达式,例如,在编译时可获得 0 值的整型 const对象或字面值常量 0。
2. 类型匹配的对象的地址。
3. 另一对象末的下一地址。
4. 同类型的另一个有效指针。
把 int 型变量赋给指针是非法的,尽管此 int 型变量的值可能为 0。但允许把数值 0 或在编译时可获得 0 值的 const 量赋给指针:
int ival;
int zero = 0;
const int c_ival = 0;
int *pi = ival; // error: pi initialized from int value of ival
pi = zero;// error: pi assigned int value of zero
pi = c_ival;// ok: c_ival is a const with compile-time value of 0
pi = 0;// ok: directly initialize to literal constant 0 [1]
除了使用数值 0 或在编译时值为 0 的 const 量外,还可以使用 C++ 语言从 C 语言中继承下来的预处理器变量 NULL,该变量在 cstdlib头文件中定义,其值为 0。如果在代码中使用了这个预处理器变量,则编译时会自动被数值 0 替换。因此,把指针初始化为 NULL 等效于初始化为 0 值 [1]
:
// cstdlib #defines NULL to 0
int *pi = NULL; // ok: equivalent to int *pi = 0; [1]
2.9 const 指针
2.9.1 指向常量的指针变量
定义:const 数据类型 *指针变量名 或 数据类型 const *指针变量名
const char *p; p = "abcd";//! 错误,指针指向的对象是一个常量,不能被赋值 p[2] = 'e'; //! 正常,可以修改指针变量p的值 p='ghjk';注意:指针变量指向的对象的值不能被改变,而指针变量的值可以改变(可以改变指向),本质是指向内容是常量,而指针还是变量。
2.9.2 指向变量的指针常量
定义:数据类型 * const 指针常量名
char str[]="abcd"; char *const p=str;//! 正确,指针p指向的对象是一个变量 p[3] = 'e'; //! 错误,指针p指向对象是一个常量 p="ghjk";注意:指针常量的值不能修改,而指针常量所指向的值是可以修改的,本质指针是常量,而指向内容是变量。
2.9.3 指向常量的指针常量
定义 :const 数据类型 * const 指针常量
char str[]="abcd"; const char * const p = str; //! 错误,指针常量所指向的对象是一个常量 p[3]='e'; //! 错误,指针常量是一个常量 p="ghjk";注意:指针常量的值和指针常量所指向的对象的值均不能改变。
2.10 void指针类型
void指针类型,可以用来指向一个抽象类型的数据,在将它的值赋给另一个指针变量时,要进行强制类型转换使之适合于赋值的变量的类型。
char *p1; void *p2;p1 = (char *)p2;//! (char *)表示强制转换成,强制将空指针转换成字符型指针2.10 int *p = 5对不对?
5是int型,p是int *型,等号两边类型不匹配。
写成int *p=(int *)5;就对了。把整数5 强制类型转换为整型指针, 在赋值给整型指针p,这个时候p 指向 地址 0x00000005 所表示的内存空间
三. 指针与数组
3.1 一维数组的指针表示方法
a[i] 下标法
*(a+i) 地址法
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 定义一维数组int array[] = {1,3,5,7,9};//! 定义指针int *p;//! 下标法for (int i = 0; i < 5; i++){qDebug("array1 = %d",array[i]);}//! 地址法for(int i = 0; i < 5; i++){qDebug("%array2 = %d",*(array+i));}//! 指针法/*for (p = array; p < array+5; p++){qDebug("array3=%d",*p);}*//* p = array;for (int i = 0; i < 5; i++,p++){qDebug("array3=%d",*p);}*/for (int i = 0; i < 5; i++){qDebug("array3=%d",*(p+i));}return a.exec(); }以上几种表示方法均可以,但如下几种方式不可以,因为a的值是数组首地址,它是一个常数,其值是不能自增的。指针p是变量,p的值可以不断变化,而p的值是地址,因此p可以不断改变其指向。
for (int i = 0; i < 5; i++){qDebug("array=%d",a++); }3.2 二维数组的指针表示方法
| 含义 | 表示形式 | 备注 |
| 第0行第1列元素地址 | a[0]+1,*a+1,*(a+0)+1,&a[0][1] | |
| 第0行第1列元素的值 | *(a[]0]+1),*(*a+1),a[0][1] |
指针的类型取决于它所指向的对象,如果将p=a[0]改写成p=a就会出错,虽然是a[0]和a的值相同,但是a是二级指针,而a【0】是一级指针,二者的指向的对象不同,类型不同;
二维数组中的一级指针和二级指针可以用行指针和列指针来说明,二级指针(数组名)指向行,一级指针用来指向的是某行的中的列。指向行和指向列的指针不是同一类型的指针。
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int array[2][2]={1,2,3,4};int (*p)[2];p =array;for (int i = 0; i < 2; i++){for (int j = 0; j < 2; j++){qDebug("array=%d",*((*p+i)+j));}}return a.exec(); }上列中p是指向行的指针变量,因此应该赋予它行地址,于是p=a是合法的。
3.3 指针与字符数组
字符串是存放在字符数组中的。因此为了对字符串操作,可以定义一个字符数组,也可以定义一个字符指针,通过指针的指向来访问所需要的字符。
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);char string[]="C Language";char *p;p = string;qDebug("%s",string);qDebug("%s",p);return a.exec(); }常用的字符串函数:
| 1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
| 2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
| 3 | strlen(s1); 返回字符串 s1 的长度。 |
| 4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回值小于 0;如果 s1>s2 则返回值大于 0。 |
| 5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
| 6 | strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
3.4 指针数组与数组指针
3.4.1 指针数组
指针数组:首先是一个数组,而数组的元素是指针,也就是说,如果数组元素都是相同类型的指针,则称这个数组为指针数组。所谓相同类型的指针是说指针所指向的对象类型是相同的。指针数组是数组元素为指针的数组,其本质为数组。
定义形式:int *p【10】
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 指针数组int ix=0;int array[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};int *p[3];for(ix;ix<3;ix++) p[ix]=array[ix];for(int i = 0;i<3;i++){for(int j=0;j<4;j++)//注意这里的j要赋值等于0,要不然只能输出第一行1234,达不到预期的效果{qDebug("%d",p[i][j]); //或者 *(*(p+i)+j) 或者 *(p[i]+j)}}}return a.exec(); }3.4.2 数组指针
数组指针是指向数组首元素的地址的指针,其本质为指针(这个指针存放的是数组首地址的地址,相当于2级指针,这个指针不可移动);
定义方法:(*指针变量名)[长度]即(*P)[n]
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);//! 数组指针int array[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};int (*p)[4];//p=(int(*)[4])array;p =array;for(int i=0;i<3;i++){for(int j=0;j<4;j++) {qDebug("%d",p[i][j]); //或者 *(*(p+i)+j) 或者 *(p[i]+j)}}return a.exec(); }3.4.3 二者区别
1) 定义形式:
指针数组:int *p【10】
数组指针:int (*p)【10】
“[]”的优先级比“*”要高。p1 先与“[]”结合,构成一个数组的定义,数组名为p1,int *修饰的是数组的内容,即数组的每个元素。那现在我们清楚,这是一个数组,其包含10 个指向int 类型数据的指针,即指针数组。至于p2 就更好理解了,在这里“()”的优先级比“[]”高,“*”号和p2 构成一个指针的定义,指针变量名为p2,int 修饰的是数组的内容,即数组的每个元素。数组在这里并没有名字,是个匿名数组。那现在我们清楚p2 是一个指针,它指向一个包含10 个int 类型数据的数组,即数组指针。我们可以借助下面的图加深理解:
2) 所占存储空间的区别
数组指针只是一个指针变量,是C 语言里专门用来指向二维数组的,它占有内存中一个指针的存储空间。指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间.
3)应用上的区别
指针数组一般用于处理二维数组。指向一维数组的指针变量用于处理二维数组也是非常方便的。
数组指针和指针数组在处理同一个二维数组时,数组指针的元素个数和指针数组的数组长度不相同,数组指针的元素个数和二维数组的列长度相同。 而指针数组的数组长度和二维数组的行长度相同。
在处理字符串的问题上,使用指针数组处理就比使用数组指针方便多了。因为多个字符串比用二维字符数组处理字符串更加方便,更加的节省内存空间。
相比于比二维字符数组,指针数组有明显的优点:一是指针数组中每个元素所指的字符串不必限制在相同的字符长度;二是访问指针数组中的一个元素是用指针间接进行的,效率比下标方式要高。 但是二维字符数组却可以通过下标很方便的修改某一元素的值,而指针数组却无法这么做。
#include <QCoreApplication>int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int i;const char *string[3] = {"Mei","Zuo","Chuan"};const char* p;if (strcmp(string[0],string[1]) > 0){p = string[0];string[0] = string[1];string[1] = p;}if (strcmp(string[0],string[2]) > 0){p = string[0];string[0] = string[2];string[2] = p;}if (strcmp(string[1],string[2]) > 0){p = string[1];string[1] = string[2];string[2] = p;}for(i=0;i<3;i++){qDebug("%s, ",string[i]);}return a.exec(); }4. 指针与函数
4.1 指针作为函数参数
#include <QCoreApplication> void sub(int *px,int *py){qDebug("sub:%d",*px-*py); }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int x = 20,y = 10;sub(&x,&y);return a.exec(); }用指针(地址)作为函数参数,可以“实现通过被调用的函数改变主函数中变量的值”的目的。
4.2 数组指针作为函数参数
| 实参 | 形参 |
| 数组名 | 数组名 |
| 数组名 | 指针变量 |
| 指针变量 | 数组名 |
| 指针变量 | 指针变量 |
数组名代表数组的起始地址,用数组名作为参数传递的是地址(将数组起始地址传给被调用函数的形参),既然地址可以作为参数传递,那么指向数组的指针变量也可以作为函数参数。
#include <QCoreApplication>int arr_add(int *arr,int n){int sum = 0;for (int i = 0; i < n; i++){sum = sum+*(arr+i);}return sum; }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int array[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};int *p,total;p = array[0];total = arr_add(p,12);qDebug("%d",total);return a.exec(); }4.3 指向函数的指针(函数指针)
一个函数包括一系列的质量,在内存中占据一片存储单元,它有一个起始地址,即函数的入口地址,通过这个地址可以找到该函数,这个地址就称为函数的指针
函数指针是指向函数的指针变量。 因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。
定义:类型标识符 (*指针变量名)(形参列表)
int (*p)(),它表示p指向一个返回整型值的函数,注意*p两侧的括弧不能省略,如果写成int *p()就成了返回指针值的函数。
#include <QCoreApplication>int arr_add(int *arr,int n){int sum = 0;for (int i = 0; i < n; i++){sum = sum+*(arr+i);}return sum; }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int array[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};int *p,total;p = array[0];int (*pt)(int *arr,int n);pt = arr_add;total = (*pt)(p,12);qDebug("%d",total);return a.exec(); }注意:在用指针变量调用函数之前,应先将函数入口地址赋给指针变量。
4.4 指针函数
指针函数是一个函数。函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
类型名 *函数名(函数参数列表);
#include <QCoreApplication>int* arr_add(int *arr,int n){static int sum = 0;int *point;point = ∑for (int i = 0; i < n; i++){sum += *(arr+i);}return (int *)point; }int main(int argc, char *argv[]) {QCoreApplication a(argc, argv);int array[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};//float arr_add(),arr_avr();int *p;p = array[0];int* (*pt)(int *arr,int n);pt = arr_add;int *total = (*pt)(p,12);return a.exec(); } 输出: 78如果static去掉,这样是不对的。因为sum是个局部变量,返回的是sum的地址的副本。而该函数在结束时会回收sum,sum地址指向的值会被后续的别的函数分配和更改。如果使用该指针指向的值会是不确定的。因此才有“永远不要从函数中返回局部自动变量的地址“。
4.5 main函数的参数
main(int argc,char *argv[]),第一个形参argc是个整型变量,第二个形参argv是一个指针数组,其元素指向字符型数据。
int main(int argc,char *argv[]){while(argc > 1){++argv;printf("%s",*argv);--argc;} }如果从键盘输入的命令:cfile cmputer C_Language
则输出:
computer
C_Language
5. 指针小结
| 定义形式 | 含义 |
| int *p; | p指向整型数据的指针变量 |
| int (*p)[n] | p为指向含有n个元素的一维数组的指针变量,数组指针 |
| int(*p)() | p为指向函数的指针,该函数返回一个整型值,函数指针 |
| int *p[n] | 定义指针数组,它含有n个元素,每个元素指向一个整形数据,指针数组 |
| int *p() | p为带回一个指针的函数,该指针指向整型数据,指针函数 |
| int **p | p是一个指针变量,它指向一个指向整型数据的指针变量,即指向指针变量的指针,二级指针 |
| int (**p)[n] | p是一个指向另一个指针变量的指针变量,被指向的指针变量指向一个含有n个整型数据的一维数组 |
总结
- 上一篇: 深度学习之图像处理---七级浮屠
- 下一篇: 深入浅出之函数