指针 这是利用指针来修改值的一个简单演示
指针的概念: 1 2 3 两种理解方式 1、指针就是地址,地址就是指针; 2、指针变量,其实是C/C++的一种变量。这种变量比价特殊,通常会被赋值为某变量的地址 (p = &a),然后利用 *p 的方式来间接访问 p 所指向的地址
指针的定义和初始化 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 野指针:指针变量未经赋值,或者指向非法地址
C语言这里是没有默认初始化的,所以这里是野指针错误,有的语言会进行初始化
全局变量是会初始化的
指针大小 1 2 3 4 5 两种情况 32位,64位 4字节,8字节 现在电脑程序一般是64位,手机是32位
指针使用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; // 错误,指针常量不能修改指针本身的值
指针应用 1 2 3 4 5 指针应用: 实现函数参数引用传递 实现函数返回多个值 方便处理字符串 表示复杂的数据结构(结构体等),在作为参数传递的时候更节省内存
返回多个值是一种表述:返回值只有一个,但是可以通过利用向方法中传递指针,在函数体内修改指针指向的内容来实现多个参数改变
为什么说传递复杂数据结构的时候可以节省内存呢,因为是传递的地址值,不需要将这个复杂的数据结构赋值给另一个地址值来进行操作
数组指针 先看一下数组
int数组和char数组
看注释
1 2 char arr_str[] = {'h', 'e', 'l', 'l', 'o', '\0'} 这样定义一个char数组可以正常输出
字符串数组
字符串数组在C中表示是char数组的数组
1 2 3 4 5 6 7 char arr_str[][] = {"hello", "world", "c++", "run"} // 这个时候第二个括号内需要填写数值,这个数值不能小于最长的那个字符串长度 // 使用const char* 可以定义一个字符串,在字符串的基础上再创建一个数组就是字符串数组了,这个时候就没上上面的限制了 // 上面说的是C语言中的字符串数组定义方法,C++中有string,可以直接定义
再来看指针
int数组指针
char数组指针
字符串数组
int * 指向int 类型
int**指向int*类型这个很好理解的吧
指针运算 指针运算就是上面使用的
1 2 3 4 5 cout << *p++; cout << *(++p); cout << *(P+1); // 这些都是指针运算
它的这几个运算方式是不同的
1 2 3 4 5 6 *p++ 和 *++p 这两种方法是针对p本身进行操作,p自己+1 而,*(P+1)是先运算再取值,并且不对p本身的大小进行操作 而*p++ 和 *++p 也是有差别的,看下面的输出,*p++是先输出*p然后再进行++操作,而*++p是先进行++操作再取值
函数参数的引用传递 基本数据类型 之前说过函数值的传递在Java中基本数据类型是做不到的
但是也有其他方法,比如放到数组,或者在一个类当中,数组已经演示过了,下面是在类中更改,两者的底层原理是一样代表,都是传入地址值,直接操作地址值内的数
C++
将地址传入方法相当于
1 2 int * a = &a; int * b = &b
数组 已经演示过了
Java
C++:
数组传递的是地址值可以直接操作,原因是
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; // 因为函数和数组一样,函数名就是函数地址
1 2 3 函数指针的调用 (*pFunc)(&a, b); pFunc(&a, b); // 这种调用方法仅限于函数指针,其他指针不可以使用
函数指针数组 1 2 void (*pFunc[2])(int *, int *); // 这个表示这个函数指针可以放三个函数
函数指针作为参数 函数指针可以作为一个参数传到另一个函数内,函数指针还可以放到结构体内,结构体只能放基本数据类型,不能定义函数和方法,但是可以存放函数指针
1 2 3 将函数指针放到了函数内,在函数内执行了另一个函数 定义函数指针,指定函数,使用函数指针调用函数
全局变量 1 2 3 全局变量: 本文件中的全局变量定义 引用别的文件中定义的全局变量
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、静态函数 限制在本文件中使用
内存四区 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; }
利用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数组的值就可以随意修改了
内存操作 还是写加密后数据转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指向指向内存空间的指针, // 任何一个指向这块内存空间的指针都可以释放这块空间
指针的注意事项 1 2 3 4 不要把函数内部的局部变量地址,交给函数外的指针变量 // 函数执行完毕局部变量会被销毁,那么指针也就成为了野指针 // 如果需要局部变量,可以传入参数指针,把参数当作返回值使用
多级指针 1 2 3 4 5 6 概念:指向指针的指针 例: int a = 100; int * p = &a; int ** pp = &p; int *** ppp = &pp;
多级指针的应用
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 *数组
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); }
结构体指针 1 2 3 okl * pp = &ok; (*pp).pFunc(a); pp->pFunc(a);
结构体指针还有一种调用方式,就是箭头调用,可以去掉一层*
结构体传参 1 2 值传递: 如果一个结构体数组,很占内存 引用传递: 指针4或者8个