指针
地址编号: 存的是值
指针: 存储的是地址编号值 的数据类型 是地址编号的数据类型,强调的是类型
指针变量: 数据类型为指针的变量,用于保存地址编号
地址编号
概述:
地址编号是内存每一个字节的编号统称。
如
int n = 10;
在内存分配了4个字节
每个进程(运行着的程序)都有寻址范围
32位机的寻址范围时4G,0x00 00 00 00~0xff ff ff ff.
系统会为每一个字节分配一个32位的地址编号。便于系统管理内存
32位机的地址编号占4字节
64位机的地址编号占8字节
指针与指针变量
指针:是地址编号的数据类型,强调的是类型
指针变量:数据类型为指针的变量,用于保存地址编号
注意
指针变量定义
语法:
数据类型 * 变量名;
注意:
数据类型: 指针变量指向的地址中存储的数据 的数据类型
1,不同的指针只能存储对应的数据类型的地址
如:
整型的指针变量,只能存放整型变量的地址
2,如果在一行中定义多个指针变量,每个指针变量前面都需要加 * 来修饰
如:
int *p1; char *p2; float *p3; double *p4; void *p5; ... int nums[] = {1,2,3,4,5}; int *p6 = nums; int *p1,*p2,*p3;
指针变量的初始化
注意
1,当指针变量为局部变量,并没有为其赋初始值,此时因为是局部变量,所以系统为其赋 值为随机数,此时我们并不知道指针变量存
储的地址是什么.这种指针我们称为野指针
2,指针变量没有指向合法的地址建议初始化为NULL,这种指针我们称为空指针
int *p = NULL; //空指针: 是NULL 不是0 0也有地址 而NULL 没有
int *p; //因为没有初始化所有初始化随机 所以就不知道指向哪里了 就是野指针
指针运算符
&
作用:取地址
int a = 10; int *p = &a; printf("%p\n",p); //此时打印的是p的值即a的地址
注意:
栈:静态全局区可取地址
堆,常量池,代码区不可取
*
作用:取值
printf("%d\n",*p); //将p中存储的地址中的值获取
作用**:改值**
*p = 值;
注意事项
野指针与空指针取值操作会出现段错误
哪怕用空指针也不用野指针 虽然空必然报错 但是错误是显性的 野指针对程序一定是坏的
因为 空指针 必报错 而野指针可能会在合法地址内也可能不在 所以 就很难排错
int *p = NULL; //空指针: 是NULL 不是0 0也有地址 而NULL 没有
int *p; //因为没有初始化所有初始化随机 所以就不知道指向哪里了 就是野指针
指针的数据类型
int *p;
char *p;
口诀:
将指针变量名的变量名去除,剩余的就是指针变量的数据类型。
int *p 去掉变量名p 那就是 int *
指针存储的数据类型
int *p;
char *p;
口诀:
将指针变量的 变量名与最近的一个* 去除,剩余的就是指针变量存储地址对应的数据类型 int *p; 去掉 *p 就是int 型
作用:
分析等号两边数据类型一致
二级指针
概念:
又名 指针的指针 因为指针变量本质也是一个变量,在32位机中占4字节,64位机占8字节
如:
int a = 10; //假设此时a在栈内存中的地址编号为0x00 00 00 01,长度4字节 int *p = &a; //将a的地址赋值给变量p,此时p存储的地址为a的地址即0x00 00 00 01 p本身也需要占用内存,其地址假设为 0x 00 00 00 06 int **p = &p; // 将p的地址 0x 00 00 00 06赋值给p2
1,二级指针使用 *取出的是什么? 取出的是存储的一级指针本身的地址,不是一级指针存储的地址 int num01=10; int num02=1; int *pl = &num01; int *p2 = &num02; *pl = 100; //通过 * 但核心还是地址来改变值 printf("num01 = %d\n",num01); // 100 因为使用取值与改值 *将指针p存储的数据10进行了改值 printf("num02 = %d\n",num02); // 1 int **p3 =&p1; //将p1本身的地址赋值给p3 此时p3存储的是p1本身的地址 *p3 = p2; // 将 p2 的地址赋值给 p3 printf("**p3 = %d\n",**p3); // 1 **p 是* *p3 也就是取*p3地址存的值 还是num02
void与指针
void的作用:
万能指针的作用 核心作用就是作为函数的形参。
1 void 作为函数的返回值类型,表示没有返回值或返回值为NULL; 2 void 与指针结合称为万能指针 ,可有存储任何一种类型变量的地址。 但是不能直接对 void *p 中的p 直接取值 因为 无法确定p的取值宽度 。 int num = 10; int *p1 = # char c = 'a'; char *p2 = &c; void *p3 = # void *p4 = &c; p3 = &c; //修改p3存储的地址
万能指针的作用:
- 核心作用就是作为函数的形参。
万能指针的注意事项:
- 不能直接对 void *p 中的p 直接取值 因为 无法确定p的取值宽度 。
const与指针
常量指针
可以修改其存储的地址 ,无法修改值。
常量指针 是指针 可以修改其存储的地址,但是无法修改其存储的值。
语法:
数据类型 const *指针名称; 或者 const 数据类型 *指针名称;
示例:
#include <stdio.h> int main(int argc, char const *argv[]) { int a = 10; int b = 1; //常量指针,本质是指针,可以修改其存储的地址,但是无法修改其存储的地址对应的值 //const int *p = &a; 两种写法 写法a int const *p = &a; //写法b p = &b;//改变p存储的地址 //*p = 100;//改变p存储的地址对应的值,此处报错 return 0; }
指针常量
可以修改其存储的值 ,无法修改存储地址。
指针常量是常量 该指针存储的地址无法被修改,但是可以修改其存储的地址对应的值。
语法:
数据类型 * const 指针名称; int * const p;
常量指针常量
无法修改其存储的值 ,也无法修改存储地址。
概念: 常量指针指向常量
语法:
const 数据类型 * const 指针名; 或 数据类型 const * const 指针名;
示例
#include <stdio.h> int main(int argc, char const *argv[]) { int a = 10; int b = 1; //常量指针常量 //const int * const p = &a; int const * const p = &a; p = &b;//不可以改变p存储的地址 *p = 100;//也不可以改变p存储的地址对应的值 printf("a = %d\n",a); return 0; }
数组与指针
数组名的本质
数组名 就是 数组中第一个元素的首地址
printf("arr的地址: %p\n",arr); // 求数组名地址
printf("arr[0]地址:%p\n",arr[0]); //求地址首地址
// 发现结果一样
数组名 就是 数组中第一个元素的首地址
#include <stdio.h> int main(int argc, char const *argv[]) { // 证明数组名就是数组中第一个元素的地址 int arr[] = {1, 3, 5, 7, 10}; printf("arr的地址: %p\n", arr); // 使用 (void *) 强制转换为 十六进制地址数 printf("arr[0]地址:%p\n", (void *)&arr[0]); // 一维数组怎么操作 二维完全可以复刻 先定义指针赋值 // 指针可以通过下标访问数组元素,这与数组名的使用方式相同: // 定义一个指向int的指针p,初始化为数组arr的首地址 int *p = arr; printf("arr[1] = %d\n", arr[1]); // 3 下标获取数组下标为1的元素 printf("p[1] = %d\n", p[1]); // 3 指针访问数组下标为1的元素 printf("*(p + 1)=%d\n", *(p + 3)); // 7 *(p+1) 等价于 p[1] printf("*P+1= %d\n", *p + 3); // 4 这里是对*p 也就是数字取值后加1 不是位移单位 printf("p[0]= %d\n", p[0]); // 1 p[0] 等价*p printf("数组arr的存储首地址为 %p\n", &arr); // 0x7ffd49bad8b0 printf("p的地址为: %p\n", p); // 0x7ffd49bad8b0 printf("p[0]的地址为: %p\n", &p[0]); // 0x7ffd49bad8b0 printf(" p+1的地址为: %p\n", p + 1); // 0x7ffd49bad8b4 // 这里的+1是加步长 步长取决数组数据类型的大小 比如是int 那就是加一步长 而此时的一步长就是4字节 printf("*p+1=%d\n", *p + 1); // 2 核心思想 * 取值符 就是将此地址存储的数值拿出来 // *p 就是将p存储的值拿出来了 也就是1 再后移一位 所以是2 printf("*(p+1)=%d\n", *(p + 1)); // 3 // 将当前位置+1个步长,取的是数组下标为1的元素 return 0; }
事例2:
#include <stdio.h> int main(int argc, char const *argv[]) { char str[] = "hIc"; char *p = str; // 此时数组中存储的元素为char,一个char的长度为1字节,顾此时1个步长=1字节 printf("%c\n", *(p + 1)); printf("%c\n", *(p + 2)); printf("%c\n", *(p + 3)); printf("%c\n", *p + 1); //*p获取的是其地址对应的值,其地址的值为h,h+1=i printf("%c\n", (*p) + 1); return 0; }
指针数组与数组指针
概述
数组指针(数组的指针):指向数组,本质是指针
指针数组(存储指针的数组):本质是一个数组
示例1
#include <stdio.h> int main(int argc, char const *argv[]) { int nums[] = {1, 2, 3}; // 数组指针(数组的指针,指向数组,本质是指针) int *p = nums; int a = 1; int b = 2; int c = 3; int *p11 = &a; int *p12 = &b; int *p13 = &c; // 指针数组(存储指针的数组,本质是一个数组) int *p2[] = {p11, p12, p13}; return 0; }
示例2
#include <stdio.h> int main(int argc, char const *argv[]) { int nums01[] = {1, 2, 3, 4, 5}; int nums02[] = {11, 22, 33, 44, 55}; int nums03[] = {111, 222, 333, 444, 555}; // 二维数组 int nums04[][5] = { {1, 2, 3, 4, 5}, {11, 22, 33, 44, 55}, {111, 222, 333, 444, 555}}; // 指针数组 int *ps[] = {nums01, nums02, nums03}; printf("nums04[0][1] = %d\n", nums04[0][1]); printf("ps[0][1] = %d\n", ps[0][1]); printf("nums04[1][1]的=%d\n", nums04[1][1]); printf("(*(ps+1))[1]=%d\n", (*(ps + 1))[1]); printf("*(*(ps+1)+1)=%d\n", *(*(ps + 1) + 1)); return 0; }
示例3
#include <stdio.h> int main(int argc, char const *argv[]) { // char strs[][50] = {"GaoLei","WangChenHui","YueZheng"}; char *strs[] = {"GaoLei", "WangChenHui", "YueZheng"}; printf("strs[0] = %s\n", strs[0]); // GaoLei printf("strs[1] = %s\n", strs[1]); // WangChenHui printf("strs[2] = %s\n", strs[2]); // YueZheng printf("*(strs+0) = %s\n", *(strs + 0)); // printf("*(strs+1) = %s\n", *(strs + 1)); printf("*(strs+2) = %s\n", *(strs + 2)); printf("(*(strs + 0))+3 = %s\n", (*(strs + 0)) + 3); printf("(*(strs + 0))[3] = %c\n", (*(strs + 0))[3]); return 0; }
示例4
#include <stdio.h> int main(int argc, char const *argv[]) { // char strs[][50] = {"GaoLei","WangChenHui","YueZheng"}; char *strs[] = {"GaoLei", "WangChenHui", "YueZheng"}; printf("strs[0]的地址=%p\n", &strs[0]); printf("strs的地址=%p\n", strs); printf("&strs的地址=%p\n", &strs); char *p; p = strs; // char **p2 = &p; // char **p2 = &strs; char **p2 = strs; printf("*p2 = %s\n", *p2); printf("**p2 = %c\n", **p2); printf("*(p2+1) = %s\n", *(p2 + 1)); printf("*(p2+2) = %s\n", *(p2 + 2)); printf("(*(p2+1))+4 = %s\n", (*(p2 + 1)) + 4); return 0; }
#include <stdio.h> int main(int argc, char const *argv[]) { char arr[] = "xxz"; char *p = arr; // ASCII码值 是 97-122 也就是 小写 a-z // 大写字母的 ASCII 码值范围是 65 到 90 printf("*p+1== %c\n", *p + 1); //*p得到的是 p[0] 也就是 x的ASCII 码值 120 然后加一 return 0; }
步长:
通常指的是数组中元素的内存间隔。
步长 = 数组中存储的元素的数据类型所占的字节数 当为int 那加1 就是加一步长也就是加4 当为char 一步长就是1 加1 就是加1
函数与指针
函数名的本质
函数名本质上就是函数在代码区存储的首地址
指针作为形参
函数指针
作用:记录函数的地址 语法: 返回值类型 (*指针名称)(指向的函数的形参列表的数据类型) = 函数名; 注意: 形参列表的数据类型可有可无 函数指针调用函数 指针名 (实参列表); 变量名 = 指针名(实参列表);
事例:
#include <stdio.h> extern void add(int a, int b); extern void sub(int a, int b); extern void test(void (*p)(int, int)); int main(int argc, char const *argv[]) { printf("main函数的地址=%p\n", main); int (*p)(int, char *[]) = main; // 返回值类型 (*指针名称)(指向的函数的形参列表的数据类型) = 函数名; void (*p2)(int, int) = add; // 函数指针调用函数 // 指针名(实参列表); // 变量名 = 指针名(实参列表); // p2 = sub; p2(1, 2); test(p2); return 0; } void test(void (*p)(int, int)) { p(10, 12); } void add(int a, int b) { printf("%d+%d=%d\n", a, b, a + b); } void sub(int a, int b) { printf("%d-%d=%d\n", a, b, a - b); }
指针作为形参
#include <stdio.h> extern void showArray(int nums[],int len); int main(int argc, char const *argv[]) { int nums[] = {1,2,3,4,5}; showArray(nums,5); return 0; } void showArray(int *nums,int len) { for (int i = 0; i < len; i++) { // printf("%d\t",nums[i]); printf("%d\t",*(nums+i)); } printf("\n"); }
指针作为返回值
#include <stdio.h> #include <stdlib.h> #include <time.h> // extern int* getNums(); extern void getNums(int nums[],int len); int main(int argc, char const *argv[]) { srand(time(NULL)); // int *p = getNums(); // for (int i = 0; i < 10; i++) // { // printf("%d\t",p[i]); // } // printf("\n"); int nums[10] = {0}; getNums(nums,10); for (int i = 0; i < 10; i++) { printf("%d\t",nums[i]); } printf("\n"); return 0; } // int* getNums() // { // static int nums[10] = {0}; // for (int i = 0; i < 10; i++) // { // nums[i] = rand() % 100; // } // return nums; // } void getNums(int nums[],int len) { for (int i = 0; i < len; i++) { nums[i] = rand() % 100; } }