IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言----指针 -> 正文阅读

[C++知识库]C语言----指针

C语言-指针:

在已有的数据类型后面加上一个星号(char*、short*、int*…),这就是一个指针。

指针是一种新的数据类型,学习一个新的数据类型我们应该从:数据宽度、数据声明方式、数据赋值方式、数据运用操作这些方面来了解这个新类型。

指针的声明

char* a;
short* b;
int* c;
float* d;
double* e;
struct student* f;
char** a;
short*** b;
int**** c;
float***** d;
double****** e;
struct student******* f;

1、指针的标准写法:变量类型* 变量名

2、任何类型都可以带* 加上*以后就是新的类型

3、*可以是任意多个

? 指针的赋值

struct A{
	int x;
	int y;
	int z;
};

void test(){
    char* a;
    short** b;
    int*** c;
    struct A* d;
    
    a=(char*)1;
    b=(short**)2;
    c=(int***)3;
    d=(struct A*)4;
}

1、在赋值的时候要在变量前面用小括号括起来,然后在括号内填入类型

2、类型在填写时我们的星号也要和声明时类型的星号一样

3、指针的宽度永远是4字节、无论类型是什么,无论有几个*

指针的+、-运算

?

++、–运算

void test(){
    char* a;
    short* b;
    int* c;
    char** d;
    short** e;
    int** f;
 
    a=(char*)1;
    b=(short*)2;
    c=(int*)3;
    d=(char**)4;
    e=(short**)5;
    f=(int**)6;
    
	printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f);
    //>>>结果:1,2,3,4,5,6
    a++;
    b++;
    c++;
    d++;
    e++;
    f++;
    
    printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f);
    //>>>结果:2,4,7,8,9,10(1+1,2+2,3+4,4+4,5+4,6+4)
    a--;
    b--;
    c--;
    d--;
    e--;
    f--;
    
    printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f);
    //>>>结果:1,2,3,4,5,6(2-1,4-2,7-4,8-4,9-4,10-4)
    //其中的+1、+2、+4这些都是char、short、int对应的宽度
}

?

+、-运算

void test(){
    char* a;
    short* b;
    int* c;
    char** d;
    short** e;
    int** f;
 
    a=(char*)1;
    b=(short*)2;
    c=(int*)3;
    d=(char**)4;
    e=(short**)5;
    f=(int**)6;
    
	printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f);
    //>>>结果:1,2,3,4,5,6
    
    a=a+5;
    b=b+5;
    c=c+5;
    d=d+5;
    e=e+5;
    f=f+5;
    
    printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f);
    //>>>结果:6,12,23,24,25,26(1+5*1,2+5*2,3+5*4,4+5*4,5+5*4,6+5*4)
    
    a=a-5;
    b=b-5;
    c=c-5;
    d=d-5;
    e=e-5;
    f=f-5;
    printf("%d,%d,%d,%d,%d,%d\n",a,b,c,d,e,f);
    //>>>结果:(6-5*1,12-5*2,23-5*4,24-5*4,25-5*4,26-5*4)
    //其中的*1、*2、*4这些都是char、short、int对应的宽度;
}

1、指针类型的变量可以加、减一个整数,但是不能乘或者除

2、指针类型变量与其他整数相加或者相减时:

? 指针 + N = 指针 + N * (去掉一个*后类型的宽度)

? 指针 - N = 指针 - N * (去掉一个*后类型的宽度)

?

指针的加减

编译器限制了两个指针不能相加,但是可以相减

void test(){
    int x,y,z,l;
    
    char* a;
    char* b;
    char** e;
    char** f;    
    short* c;
    short* d;
    short** g;
    short** h;
    
    a=(char*)1;
    b=(char*) 2;
    e=(char**) 3;
    f=(char**) 7;    
    c=(short*) 5;
    d=(short*) 9;
    g=(short**) 7;
    h=(short**) 19;
    
    x=b-a;
    y=f-e;
    z=d-c;
    l=h-g;
    
    printf("%d,%d,%d,%d,",x,y,z,l);
    //>>>结果:1,1,2,3,((2-1)/1,(7-3)/4,(9-5)/2,(19-7)/4)
}

1、两个指针进行相减时,先进行数字的相减,再将结果除以数据宽度(去*后)

2、相减只能是通类型的相减(int和char之间不能相减,星号数不同不能相减)

3、两指针相减后得到是int型的数据,不能用char*类型装

?

指针比较

指针是可以进行比较的

void test(){
    char* a;
    char* b;
    
    if (a>b){
        printf("1");
    }
    else{
        printf("2");
    }
}

不同类型的指针进行比较会出现警告,但是还是能正常运行(vc++2010)

//练习---看懂反汇编
struct A{
	int x;
	int y;
};
void test1(){
   struct A** x;

   x=(struct A**)100;

   x++;
   printf("%d\n",x);

   x=x+2;
   printf("%d\n",x);

   x=x-3;
   printf("%d\n",x);
}

void test2(){
	struct A** x;
	struct A** y;
	int z;

	x=(struct A**)200;
	y=(struct A**)100;
	z=x-y;
	printf("%d\n",z);
}

void test3(){
	struct A* x;

	x=(struct A*)100;

    x++;
    printf("%d\n",x);

    x=x+2;
    printf("%d\n",x);

    x=x-3;
    printf("%d\n",x);
}

void test4(){
	struct A* x;
	struct A* y;
	int z;

	x=(struct A*)200;
	y=(struct A*)100;
	z=x-y;

	printf("%d\n",z);
}
int main(){
	test1();
	test2();
	test3();
	test4();
	return 0;
}

?

类型转换

主要是编译器设置,看编译器。

1、基本类型之间都可以转换,但是结构体和其它类型之间不可以转换

2、(int*)可以转换成(char *),指针之间是可以转换的,结构体的指针也是可以转换的

?

地址符号:&

&(地址符号):可以取任何一个变量的地址

&a的类型就是a的类型 + *

void test(){
    char a=10;
    short b=20;
    //&a==char*
    //&b==short*
}
//小练习----写出反汇编
void test(){
   char a = 10;
	short b =20;
	int c =30;

	char* pa = &a;
	short* pb = &b;
	int* pc = &c;

	char** ppa = &pa;
	short** ppb = &pb;
	int** ppc =&pc; 
}

每一个存储单元都对应了一个唯一的物理地址!!!

这里相当于有两个变量:地址内存单元 我们将数据放入到内存单元中,而这个内存单元有其对应的唯一的地址。指针就是变量的地址!我们可以通过找到地址来获取对应的数据(内存单元信息)。

//小练习---写出反汇编
void test(){
    int p = 10;
    
    int******* p7;
    int****** p6;
    int***** p5;
    int**** p4;
    int*** p3;
    int** p2;
    int* p1;
    
    p1 = &p;
    p2 = &p1;
    p3 = &p2;
    p4 = &p3;
    p5 = &p4;
    p6 = &p5;
    p7 = &p6;
}
//小练习---写出反汇编代码
void test(){
    int x,y;
	int* px;
	int** px2;
	int*** px3;
	int**** px4;
	int***** px5;
    
    x = 10;
    px = &x;
    px2 = &px;
    px3 = &px2;
    px4 = &px3;
    px5 = &px4;
    
    y = *(*(*(*(*(px5)))))
}

? 指针的进一步探索:*x

char* x 对应的数据类型是 char

short* x 对应的数据类型是 short

int* * x 对应的数据类型是 int *

*x 的意思就是取x中的值

//小练习---补充代码
void function(){
    int arr[5]={1,2,3,4,5};
    //将数组中的值进行倒置
    for(int k=0;k<5;k++){
        printf("%d\n",*(p+k));
    }
}

2、通过char指针遍历数据

3、通过short指针遍历数据

4、通过int指针遍历数据

?

数组作为参数

? 数组作为参数传入函数中,实质上传入的是一个基址(数组中第一个元素的堆栈地址)。在函数内部调用传入的数组参数时,是通过偏移实现的。通过在第一个数组元素的地址上进行相加来偏移到后面的数组元素的位置,实现对元素的操作。

结合数组本身的特点,数组是一块连续分配内存的存储单元,所以存储的数据是连续存放的。我们在将数组作为参数传入函数时,为了提高效率并节省资源,所以我们只传入一个数组的基址地址,后续操作通过在基址地址上做偏移来实现。这样既节省了空间,也提高了效率。

不同类型的数组作为参数其实现本质是一样的,不同点在于偏移量的改变(char型,每次偏移都只偏移1位;short型则需偏移两位,int型偏移为四位)。

?

通过指针遍历数组

? 把数组的基址给到指针,然后通过指针的偏移来一个一个遍历数据。

当我用宽字节的指针遍历窄字节的数组时,我的偏移量会发生改变,会导致读取到的数据和预期不同。

如:我们设置的是char型的数组,我们用short指针进行遍历,我们一次会读取到两个字节的数据,然后两个字节拼凑后转换成十进制数。(char:01,02,03,04—>1,2,3,4)(short:0102,0304—>513,1027)

//小练习---编辑代码
//实现功能:下面是模拟的一块数据区,该数据区存储着角色的血值信息,血值信息为int型,值为100(十进制),通过遍历等方法查找出其值的地址。(任意连续四个字节的数都可以是血值)
void test(){
    char [100] = {
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
        0x00,0x20,0x10,0x03,0x03,0x0c,0x00,0x00,0x44,0x00,
        0x00,0x33,0x00,0x47,0x0c,0x0e,0x00,0x0d,0x00,0x11,
        0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xaa,0x00,
        0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x02,0x00,0x74,0x0f,0x41,0x00,0x00,0x00,
        0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0a,0x00,
        0x00,0x02,0x74,0x0f,0x41,0x00,0x06,0x08,0x00,0x00,
        0x00,0x00,0x00,0x64,0x00,0x0f,0x00,0x00,0x0d,0x00,
        0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
    }
}
------------------
#include <stdio.h>

char a[100] = {
        0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
        0x00,0x20,0x10,0x03,0x03,0x0c,0x00,0x00,0x44,0x00,
        0x00,0x33,0x00,0x47,0x0c,0x0e,0x00,0x0d,0x00,0x11,
        0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xaa,0x00,
        0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x02,0x00,0x74,0x0f,0x41,0x00,0x00,0x00,
        0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0a,0x00,
        0x00,0x02,0x74,0x0f,0x41,0x00,0x06,0x08,0x00,0x00,
        0x00,0x00,0x00,0x64,0x00,0x0f,0x00,0x00,0x0d,0x00,
        0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};

void find(){
	char* q;
	int* p;
	int i;
	int j;
	char b[4];
	q=&a[0];
	for(i=0;i<97;i++){
		for(j=0;j<4;j++){
			b[j]=*(q+i+j);
		}
		p=(int*)&b[0];
		//printf("%x\n",*(p));
		if(*(p)==0x64){
			printf("%x\n",p);
		}
	}
}
int main(){
	find();
	getchar();
 
}

? 指针数组

从数据角度来说,指针与普通的数据类型没有区别。指针也能存储任意数据,但是指针与普通数据类型还是有区别的。普通数据类型的数值无法直接赋值给指针类数据,如果需要赋值就需要用到强制转换。

指针的出现主要是为了存储地址,赋值时可以直接将变量的地址赋给指针。这样在编程时结合地址就能更方便的操作,而指针数组是一个连续地址连续分配的用于存储地址等变量的存储空间。

指针是一种变量,常用于存储数据地址,但它本身作为变量也有存储地址。

在这里插入图片描述

//指针数组的声明
int* arr[5] = {0};
//指针数组的赋值
arr[0] = (int*)10;
arr[1] = (int*)11;
arr[2] = (int*)12;
arr[3] = (int*)13;
arr[4] = (int*)14;

? 结构体指针

结构体指针还是和指针的本质一样,指向某个变量的地址。

//例:
//创建结构体
struct student
{
    int a;
    int b;
    int c;
}

struct student a;
//声明结构体指针
struct student* ps;
s.a = 10;
s.b = 20;
s.c = 30;



//为结构体指针赋值
ps = &s;

//通过指针读取数据
printf("%d\n",ps->a);

//通过指针修改数据
ps->a = 11;
printf("%d\n",ps->a);

?

多级指针(指针的指针)

char* p1;
//反汇编 了解*的反汇编
printf("%d\n",*p1);

在这里插入图片描述

所以读取一个指针的反汇编过程就是先读取变量p1的值,然后再通过*****再次读取。

char* p1;
//反汇编 *p1 = *(p1+0)
printf("%d\n",*(p1+0));

在这里插入图片描述

这里的反汇编代码是和直接用星号是一样的。但是过程会不一样。

在有指针读取数据时进行加减的情况下,编译器会先读取p1所存储的值并跳转到新地址,然后用新地址进行运算,最终的计算结果就是最后跳转的地址。

char* p1;
//反汇编  *(p1+0)= p1[0]
printf("%d;%d\n",*(p1+0),p1[0]);
printf("%d;%d\n",*(p1+2),p1[2]);

在这里插入图片描述
在这里插入图片描述

这里可以看出指针其实还可以有多种表达形式,不同的表达形式对应的反汇编代码是一样的,所以编译器执行的操作也是一样的。

**p1 == (p1+0) == p1[0]

//反汇编,了解**的反汇编
char** p2;
printf("%d\n",*p2);
printf("%d\n",*(*p2));
printf("%d\n;%d\n",*(*(p2+2)+3),p2[2][3]);

在这里插入图片描述

》》》printf(“%d\n”,*p2);

只有一个星号的时候,会发现即使是**char****类型,反汇编代码也依旧一样。
在这里插入图片描述

》》》printf(“%d\n”,*( *p2));

两个星号时,反汇编代码就发生了不同,多了一步读取为地址的操作。
在这里插入图片描述

》》》printf(“%d\n;%d\n”,*( *(p2+2)+3),p2[2] [3]);

在对于多级指针的运算特点进行分析:首先把p2作为一个地址把对应的数据读取出来,再进行+2运算,此时是char**类型的加法运算所以 +2 * 4;之后是char*类型的加法运算。

用数组也能够进行同样的操作。

char*** p3;
printf("%d\n",*(*(*p3)));
printf("%d\n;%d\n",*(*(*(p3+1)+2)+3),p3[1][2][3]);

在这里插入图片描述

这时候就能理解并总结出,读取一个n级指针中的数值,需要经过n次mov操作。
在这里插入图片描述

p2原理一样。

?

数组指针

学习数组指针主要是与指针数组区别。数组指针是通过指针指向数组中的元素。

//声明
int (*a)[6];
//赋值
a = (int (*)[6])10;
printf("%d",a);//---->10
//运算
a++;
printf("%d",a);//---->34

数组指针的宽度:数组指针、多级指针、结构体指针,这些其宽度都是4个字节,可用sizeof进行判定。

数组指针分配存储空间大小的判定和结构体的大小判定相似。上述声明的数组指针中,是int类型的且大小为6的数组,所以4*6=24。

//小应用
int arr[16]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};
int (*px)[3];
px = (int (*)[3])arr;
printf("%d,%d,%d,%d,%d",px,*px,*(*px),*px+1,*(*(px+1)+1));

编译器没有为px分配空间,px和* px所指向的是同一存储空间,但是*让其代表的意义不同。

px所指向的是整个数组指针,在运算过程中关于px的加减都会乘以数组指针的大小;* px只考虑指针类型。如上述所示:* (* (px+1)+1) —> 先进行px+1* 3* 4=px+12 、12+1* 4=16

即:在px存储的数组的首地址的基础上进行偏移16个字节的偏移。

?

多维数组指针

多维数组指针和一维数组指针没有太大的区别。理解了基本的一维数组的概念后在对多维指针数组的运算规则加以理解就好了。

char code[]={
    	0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x07,0x09,
        0x00,0x20,0x10,0x03,0x03,0x0c,0x00,0x00,0x44,0x00,
        0x00,0x33,0x00,0x47,0x0c,0x0e,0x00,0x0d,0x00,0x11,
        0x00,0x00,0x00,0x02,0x64,0x00,0x00,0x00,0xaa,0x00,
        0x00,0x00,0x64,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x02,0x00,0x74,0x0f,0x41,0x00,0x00,0x00,
        0x01,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x0a,0x00,
        0x00,0x02,0x74,0x0f,0x41,0x00,0x06,0x08,0x00,0x00,
        0x00,0x00,0x00,0x64,0x00,0x0f,0x00,0x00,0x0d,0x00,
        0x00,0x00,0x23,0x00,0x00,0x64,0x00,0x00,0x64,0x00
};
//一维数组指针
int (*px)[5];
px = (int (*)[5])code;
printf("%x\n",*(*(px+2)+2));
#把数组code的首地址存入数组指针px中
//运算过程:px+2*5*4+2*4

//二维数组指针
char (*py)[2][3];
py = (char (*)[2][3])code;
printf("%x\n",*(*(*(px+2)+3)+4));
//运算过程:px+2*2*3+3*3+4

//三维数组指针
int (*pz)[2][3][4];
py = (int (*)[2][3][4])code;
printf("%x\n",*(*(*(*(px+2)+2)+2)+2));
//运算过程:px+2*2*3*4*4+2*3*4*4+2*4*4+2*4

/*
过程很简单,就是数组大小乘以加数再乘以类型宽度
*/

数组指针不一定非要指向数组,指针的本质就是指向地址。在使用指针的时候也可以任意指向更灵活的使用指针。在使用不同类型的数组指针指向数组时,注意指针的类型宽度,这会决定在对指针指向地址偏移后取该地址下的几个字节的数据。

? 函数指针

//声明:返回类型(*指针名)(参数表)
int Function(int x,int y){
    return x+y;
};
int (*px)(char,int);
//赋值
//px = (int(*)(int,int))10;
//当指针类型与变量值的类型一样是就不用强制转化了,例:
px =Function;
int x=px(1,2);
//这样直接就能通过px去调用函数了

函数指针的宽度为4,因为函数内部的变量可以任意类型任意数量,所以函数的大小不能确定,以至于函数指针不能进行++、+、–、-这些操作。能进行的就是比较操作。

函数指针的意义不在于将指针指向函数地址,然后使用指针调用函数的便捷。函数指针指向的可以是数据区也可以是代码区,又因为函数指针的类型,所以用函数指针指向数据区的具有函数功能的硬编码数据也是可以执行的。这也就是壳。函数指针会在加载dll中非常有用。

#include <stdio.h>
#include <string.h>
unsigned char fun[]={
	0x55,
	0x8B,0xEC,
	0x81,0xEC,0xC0,0x00,0x00,0x00,
	0x53,
	0x56, 
	0x57,
	0x8D,0xBD,0x40,0xFF,0xFF,0xFF,
	0xB9,0x30,0x00,0x00,0x00,
	0xB8,0xCC,0xCC,0xCC,0xCC,
	0xF3,0xAB,
	0x8B,0x45,0x08,
	0x03,0x45,0x0C,
	0x5F,
	0x5E,
	0x5B,
	0x8B,0xE5,
	0x5D,
	0xC3
};
int main(){
	int x;
	int (*PFun)(int,int);
	PFun = (int (__cdecl *)(int,int))&fun;
	x = PFun(1,2);
	printf("%d\n",x);
	getchar();
}

上述代码就是将函数隐藏在数据区,因为C++2010编译器的限制,所以没能成功,显示出现了违规行为。但是从反汇编代码上看,是完全没有问题的。算是一个函数指针的小应用。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-07-20 18:34:21  更:2022-07-20 18:36:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/14 2:05:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码