0%

指针

指针

这是利用指针来修改值的一个简单演示

image-20240915175142084

指针的概念:
1
2
3
两种理解方式
1、指针就是地址,地址就是指针;
2、指针变量,其实是C/C++的一种变量。这种变量比价特殊,通常会被赋值为某变量的地址 (p = &a),然后利用 *p 的方式来间接访问 p 所指向的地址

image-20240915181314618

指针的定义和初始化
1
2
3
4
5
6
7
8
9
既然指针是一种变量,那么肯定也可以定义,也可以初始化
1、先定义再赋值
int * p; // 定义指针变量p
p = &a; // 给p赋值

2、定义的同时初始化
int * p = &a;

// 两种的效果是一样的
运算符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
有两个重要的运算符 & 和 * 
1、&:取地址值符,将他加在某个变量的前面,组合后的符号代表这个变量的地址值
int a = 100; int * p; p = &a;
表示将a的地址值赋值给p



2、*:指针符号。指针符号再指针定义和指针操作的时候,解析方法是不同的
int p; // p是一个整型变量
int * p; // p是一个指针变量,该指针指向一个整形数
使用该指针的时候,*p则代表变量p所指向的那个变量,这个过程有些也叫做解引用


符号解释:
a 代表变量a本身
p 代表指针变量p本身
&a 代表变量a的地址值
*p 代表指针变量p所指向的那个变量,也就是变量a
&p 代表指针变量p本身的地址值。符号合法,但无意义。在二级指针中使用
*a 把a看作一个指针,*a表示这个指针所指向的变量。该符号不合法
也就是说:
int a = (一个地址值)
cout << *a << endl
这样的表述是不合法的,即使是存储的地址值也应该将a的类型转成int *来进行操作
int * a = (int*)0000DE298342
指针变量的解读
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
指针变量本质上是一个变量
指针变量的类型属于指针类型
int * p:定义了一个指针类型的变量p,p能指向的变量是int
其他类型的指针:
int * pInt;
char * pChar;
float * pFloat;
double * pDouble;
各种指针类型和所指向的变量类型必须匹配,否则结果不可预知

如果非要用的话,还可以进行指针间的强转 reinterpret_cast
char c = 'a';
int * p;
p = reinterpret_cast<int *>(&c)

// 这样强转也会出现问题的,因为char占一个字节,int占四个字节,因此结果不可预知
空指针和野指针
1
2
空指针:指针指向的地址是0
野指针:指针变量未经赋值,或者指向非法地址

image-20240915214624603

C语言这里是没有默认初始化的,所以这里是野指针错误,有的语言会进行初始化

全局变量是会初始化的

指针大小
1
2
3
4
5
两种情况
32位,64位
4字节,8字节

现在电脑程序一般是64位,手机是32位

image-20240915220004310

指针使用count
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
为了防止误改,使用count来修饰指针

const int* p1 = &a; // 常量指针
int* const p2 = &a; // 指针常量
const int* const p3 = &a;






int a = 10;
int b = 20;

const int* p1 = &a; // 常量指针
int* const p2 = &a; // 指针常量
const int* const p3 = &a;

// 常量指针不能修改所指对象的值,但可以修改指针本身的值
//*p1 = 30; // 错误,常量指针不能修改所指对象的值
p1 = &b; // 正确,指针常量只能修改指针本身的值



// 指针常量只能修改指针本身的值,不能修改所指对象的值
// p2 = &b; // 错误,指针常量只能修改指针本身的值
*p2 = 30; // 正确,指针常量只能修改指针本身的值




// 指针常量不能修改所指对象的值,也不能修改指针本身的值
// p3 = &b; // 错误,指针常量不能修改所指对象的值
//*p3 = 30; // 错误,指针常量不能修改指针本身的值

image-20240915221201598

指针应用
1
2
3
4
5
指针应用:
实现函数参数引用传递
实现函数返回多个值
方便处理字符串
表示复杂的数据结构(结构体等),在作为参数传递的时候更节省内存

返回多个值是一种表述:返回值只有一个,但是可以通过利用向方法中传递指针,在函数体内修改指针指向的内容来实现多个参数改变

为什么说传递复杂数据结构的时候可以节省内存呢,因为是传递的地址值,不需要将这个复杂的数据结构赋值给另一个地址值来进行操作

数组指针

先看一下数组

int数组和char数组

看注释

1
2
char arr_str[] = {'h', 'e', 'l', 'l', 'o', '\0'}
这样定义一个char数组可以正常输出

image-20240915225132566

字符串数组

image-20240915225839433

字符串数组在C中表示是char数组的数组

1
2
3
4
5
6
7
char arr_str[][] = {"hello", "world", "c++", "run"}
// 这个时候第二个括号内需要填写数值,这个数值不能小于最长的那个字符串长度
// 使用const char* 可以定义一个字符串,在字符串的基础上再创建一个数组就是字符串数组了,这个时候就没上上面的限制了



// 上面说的是C语言中的字符串数组定义方法,C++中有string,可以直接定义

再来看指针

int数组指针

image-20240915231036804

char数组指针

image-20240915231654545

字符串数组

int * 指向int 类型

int**指向int*类型这个很好理解的吧

image-20240915232309434

指针运算

指针运算就是上面使用的

1
2
3
4
5
cout << *p++;
cout << *(++p);
cout << *(P+1);

// 这些都是指针运算

image-20240916005620645

它的这几个运算方式是不同的

1
2
3
4
5
6
*p++ 和 *++p 这两种方法是针对p本身进行操作,p自己+1

而,*(P+1)是先运算再取值,并且不对p本身的大小进行操作


而*p++ 和 *++p 也是有差别的,看下面的输出,*p++是先输出*p然后再进行++操作,而*++p是先进行++操作再取值

image-20240916010053987

函数参数的引用传递

基本数据类型

之前说过函数值的传递在Java中基本数据类型是做不到的

但是也有其他方法,比如放到数组,或者在一个类当中,数组已经演示过了,下面是在类中更改,两者的底层原理是一样代表,都是传入地址值,直接操作地址值内的数

image-20240916012435232

C++

将地址传入方法相当于

1
2
int * a = &a;
int * b = &b

image-20240916014530238

数组

已经演示过了

Java

image-20240915174303124

C++:

image-20240915223751211

数组传递的是地址值可以直接操作,原因是

1
2
3
4
5
6
7
void add(int a[]);
到解析的时候会转化为,一个int指针
void add(int * a);


一般为了能够正常输出这个数组,还会传递一个length数组长度进去
add(arr, sizeof(arr) / sizeof(int))
1
2
3
常见写法:
1、返回值为void,参数当作返回值,这时候需要用指针把参数变为引用传递
2、加密函数中,传1个context,传1个明文指针,1个明文长度,1个结果指针

函数指针

1
2
3
4
5
6
7
8
9
10
11
12
定义函数指针:	
void (*name)(指向的函数参数类型);

// 还可以进行初始化
void (*pFunc)(int *,int *) = nullptr || NULL -》 两个都可以


函数指针赋值:
pFunc = add; 或者
pFunc = &add;

// 因为函数和数组一样,函数名就是函数地址

image-20240916022520615

1
2
3
函数指针的调用
(*pFunc)(&a, b);
pFunc(&a, b); // 这种调用方法仅限于函数指针,其他指针不可以使用

image-20240916023133902

函数指针数组
1
2
void (*pFunc[2])(int *, int *);
// 这个表示这个函数指针可以放三个函数

image-20240916023746682

函数指针作为参数

函数指针可以作为一个参数传到另一个函数内,函数指针还可以放到结构体内,结构体只能放基本数据类型,不能定义函数和方法,但是可以存放函数指针

1
2
3
将函数指针放到了函数内,在函数内执行了另一个函数

定义函数指针,指定函数,使用函数指针调用函数

image-20240916024717314

全局变量

1
2
3
全局变量:	
本文件中的全局变量定义
引用别的文件中定义的全局变量

image-20240916150401967

extern

1
2
1、extern int a; 		表示引用别的文件中的全局变量a
2、extern "C" ……; 表示后续代码使用C语言进行编译,不识别标识符

静态变量

1
2
3
4
5
6
7
8
9
static:
1、静态局部变量
生命周期延长,与全局变量的生命周期一致,程序结束时销毁

2、静态全局变量
限制在本文件中使用

3、静态函数
限制在本文件中使用

image-20240916151430742

image-20240916152049596

内存四区

1
2
3
4
5
6
7
代码区:存放CPU执行的机器指令,该区域数据共享、只读
全局区:存放全局变量、全局常量、静态常量、字符串常量,程序结束后由系统操作释放
栈区:自动分配和释放,存放函数的参数、局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
C中进入堆区使用关键字malloc,Java、C++进入堆区使用关键字new

不同区域存放的数据,赋予不同的生命周期,赋予不同的权限

模拟加密后的数据转Hex编码

1
2
3
4
5
6
7
8
9
10
11
12
void swap() {
char result[3];
char * res = result;
char realResult[33] = { 0 };

for (int i = 0; i < 16; i++){
int index = i;
sprintf_s(res, sizeof(result), "%.2X", index);
strcat_s(realResult, sizeof(realResult), res);
}
cout << realResult << endl;
}

image-20240916155409022

利用sprint_s格式化去两位十六进制数,然后利用strcat_s将得到的res拼接起来到realResult中

char * 和char数组

这两个都能表示一个字符串,但是是有差别的。

char * p 是字符串常量,是放到全局区域的,这里还有提示,如果让他指向一个字符串的话,需要加上const

1
2
3
4
5
6
const char * p

这种形式的指针只能修改指向的内容的值,不能修改内容,可以
p = "asda"; 但是不能 *p[0] = 'a'

而char数组的值就可以随意修改了

image-20240916160435964

内存操作

还是写加密后数据转Hex编码这个案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void swap1() {
char temp[3];
char * reaResult = nullptr;
// 使用malloc申请内存
reaResult = (char*) malloc(33);
// 申请到的内存是没有清理的,可能有乱码,需要自己清理
memset(reaResult, 0, 33);

for (int i = 0; i < 16; i++){
int index = i;
sprintf_s(temp, sizeof(temp), "%.2X", index);
strcat_s(reaResult, 33, temp);
}
cout << reaResult << endl;
// 手动释放内存
free(reaResult);
}


// 还有一个申请内存的操作 calloc calloc会自动把申请过来的内存清理

char * realResult = (char *)calloc(33,1)

// 这里free释放,释放的是内存空间,如果reaResult没有指向申请来的内存空间,释放也就没有意义,所以要将free指向指向内存空间的指针,
// 任何一个指向这块内存空间的指针都可以释放这块空间

image-20240916162413664

指针的注意事项
1
2
3
4
不要把函数内部的局部变量地址,交给函数外的指针变量

// 函数执行完毕局部变量会被销毁,那么指针也就成为了野指针
// 如果需要局部变量,可以传入参数指针,把参数当作返回值使用

多级指针

1
2
3
4
5
6
概念:指向指针的指针
例:
int a = 100;
int * p = &a;
int ** pp = &p;
int *** ppp = &pp;

image-20240916201207128

多级指针的应用

image-20240916202414425

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
从上图可以看到int *指针可以正常使用,但是char *的时候就遇到了问题,为什么呢

全局区:
0x123456789 "kong"
0x123987654 "hello world"


main栈:
char * res = 0x123456789


swap4栈:
char * a = 0x123987654


可以看到本质上main栈内res所指向的地址值并没没有改变,这个时候就需要用到二级指针了





这里的原理刚开始我有点想不通:
char *可以看作和int等效,因为char *用来指向字符串常量的地址,如果再用一个char *来接收这个数值,那么就是进行了一个赋值,并没有用到指针,所以需要使用char **来接收地址值,就可以实现类似与int *的效果了

用二级指针来操作char *数组

image-20240916210523761

1
2
3
4
5
6
7
8
9
10
11
12
13
14
全局区:
0x123456789 "kong"
0x123987654 "hello world"


main栈:
0x123456789 char * res = 0x123456789


swap4栈:
char ** a = 0x123456789
*a = "hello world" 0x123987654

-> swap4栈中 char ** a = 0x123987654 -> main栈中 char * res = 0x123987654 -> "hello world"

1、想把参数当返回值,传入的实参是char *等指针的时候

2、传入的实参是一级指针,那么想把参数当作返回值,形参就得是二级指针

3、结论:形参要比实参多一级指针,才能把参数当返回值使用

模拟加密字符串的返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void swap5(const char ** result) {
char temp[3];
char * reaResult = nullptr;
// 使用malloc申请内存
// 申请到的内存是没有清理的,可能有乱码,需要自己清理
reaResult = (char*) malloc(33);
memset(reaResult, 0, 33);

for (int i = 0; i < 16; i++){
int index = i;
sprintf_s(temp, sizeof(temp), "%.2X", index);
strcat_s(reaResult, 33, temp);
}

*result = reaResult;
//cout << reaResult << endl;
// 手动释放内存,这个时候就不能释放内存了,因为释放之后,出了这个函数,就拿不到数据了,处理了个寂寞
//free(reaResult);
}

int main(){
const char * result = "kong";
swap5(&result);
cout << result << endl;

// 拿到地址之后再释放内存
free((void*)result);
}

image-20240916213347260

结构体指针

1
2
3
okl * pp = &ok;
(*pp).pFunc(a);
pp->pFunc(a);

image-20240916215656068

结构体指针还有一种调用方式,就是箭头调用,可以去掉一层*

结构体传参

1
2
值传递: 如果一个结构体数组,很占内存
引用传递: 指针4或者8个

image-20240916220146185