地址是内存中唯一标识符!!!
// 指针的概念:指针即地址 ,地址即指针。通过地址就可以找到变量的内存空间 从而对空间进行一系列操作
//
指针变量
//int main()
//{
// int a = 0;// 创建变量 系统会分配一个 足够容纳此变量的内存空间
// // 变量拥有自己的地址值 &a可看 调试查看内存可看
// // %p 打印地址值
//
// //printf("%p\n", &a);// 所展示的是一个 16进制的数字,64位较长 32位较短
//
// 指针
// int* pa = &a;// 创建指针变量 所创建的空间是固定的 因为指针的大小是固定的 32位下 4字节大小 64位下 8字节大小
// //printf("%p\n", pa);
// //printf("%p\n", &pa);
// // pa的值与&a相同 但是&pa不相同
// // 原因:指针变量也是变量 是存放地址的变量,也拥有自己的地址
// //
// // 左值:可以理解为变量自身的唯一标识符的地址值 a *pa 就是左值
// // 右值:可以理解为所创建的变量所开辟的空间 0 &a 就是右值
//
// // 上面所打印出的地址值 都是变量的起始地址(也就是第一个字节的地址值)
//
// // 指针解引用操作符 *
// *pa = 0x11223344;// 小端机器
// printf("%#x\n", *pa);// 访问4字节内容 0x11223344
// printf("%#x\n", *(char*)pa);// 访问一字节内容 0x44
//
// // 指针类型
// // 指针的大小是固定的,创建指针变量时的指针类型只是在对指针解引用时的 内存字节的字节访问限制(最小指针类型是 char* 解引用时只允许访问一个字节内容)
//
// // 指针 +-
// printf("%p\n", pa); // ......f8 起始地址值
// printf("%p\n", pa + 1);// ...fc int* 跳过4个字节内容
// printf("%p\n", (char*)pa + 1);// ..f9 char* 跳过一个字节内容
//
// return 0;
//}
指针大小
//int main()
//{
// // 只要是 指针 不是4字节 就是 8字节
// int* pi=NULL;// 创建指针时 记得初始化为 NULL ,避免野指针的出现
// char* pc=NULL;
// long* pl = NULL;
// double* pb = NULL;
//
// // sizeof的返回类型是size_t类型(也就是无符号数)%zd是 sizeof的专用打印 ,对未使用sizeof时 打印负数时 依旧是负数
// printf("%zd\n", sizeof(int*)); // 4/8
// printf("%zd\n", sizeof(pi)); // 4/8
// printf("%zd\n", sizeof(pc)); // 4/8
// printf("%zd\n", sizeof(pl)); // 4/8
// printf("%zd\n", sizeof(pb)); // 4/8
//
// // 指针类型 仅仅是在 解引用时对访问字节的限制
// int a = 0x11223344;
// int* pa = &a;
//
// // %x 表示打印16进制数字 小写x 打印就为小写 大写即为大写
// // %#x 表示打印16进制数字 并且打印16进制的前缀 0x.....
// printf("%x\n", *(char*)pa); // 44
// printf("%x\n", *(short*)pa); // 3344
// printf("%x\n", *pa); // 11223344
//
// return 0;
//}
//
//
//void* 泛型指针
//
//void test(void* pa)// 原参数未取地址 所以使用一级指针接收
//{
// printf("%x\n", *(char*)pa);// 44
// printf("%x\n", *(int*)pa); // 11223344
//}
//int main()
//{
// // 泛型指针 可以接收任何类型的指针变量 但是不可以直接解引用,需要强制转换为具体的指针类型
// int a = 0x11223344;
// void* pa = &a;
// printf("%x\n", *(char*)pa);// 44
// printf("%x\n", *(int*)pa); // 11223344
//
// test((int*)pa);// 传参时 也需要强转为 具体的指针类型
//
//
//
// return 0;
//}
const 修饰指针
//int main()
//{
// // const 常属性
// // 变量被const修饰后变为不可修改的常变量 但是其真实属性依旧是变量 ,只是不允许修改
//
// // const 修饰指针
// // 在 * 左边 表示修饰的是 右值,也就是所指空间的内容不可更改 但是可以改变指向的地址
// // 在 * 右边 表示修饰的是 左值,也就是所指向的地址值不可更改 但是可以改变指向的空间内容
// // 当 * 左右两边都有const时 ,表示所指内容和所指空间 均不可被修改 ,但是可以桥接一个二级指针 来改变
// int a = 0;
// //const int* pa = &a;
// const int* const pa = &a;
//
// // 会warning 4090 所指向的变量类型为不可修改 但是可以完成任务 ,不会报错 会报警告
// int** pi = &pa;// *pi 表示这是一个指针 所指向的类型为 int*
// int b = 2;
// *pi = &b;
// printf("%d\n", *pa);
//
// return 0;
//}
//
指针运算
//int main()
//{
// // 指针 - 指针 得到的是 指针与指针之间的元素个数
// // 2个前提
// // 1: 必须指向的是同一块内存空间
// // 2: 指针类型需为同类型 不然会计算不准确
// int arr[10] = { 0 };
//
// // %d 打印的为有符号整数(int) 在64位下 指针是8字节大小 所以%d 会报警告,打印类型不兼容
// printf("%d\n", (arr + 10) - arr);
//
// return 0;
//}
// 数组名理解
// 1: &数组名 表示整个数字、指向的是整个数组空间
// 2: 数组名单独出现在sizeof内部时 表示整个数组 计算的是整个数组的大小,
// 3:其他情况 数组名即首元素地址 ,
// 4: 数组在传参时 并不是传的整个数组,而是数组的第一个元素的地址,所以sizeof无法实现跨函数计算数组大小,而是实际计算的是指针大小
//
// sizeof 是操作符不是函数 ,所在乎的仅仅是变量所开辟的空间的大小,并不关心实际存储的东西,可以计算任何类型的数据大小
//
// strlen 是函数 ,仅仅只能计算 字符串 的字符个数(不包括\0),统计的个数是\0之前所现的字符个数,如果没有 \0 就会导致越界,所得到的结果是未知的
// assert 需要包含头文件 assert.h
// 对指针进行检查是否为空 多用于函数调用时的指针检查
// assert 的检查仅仅只能作用在 debug模式下 release 模式下会直接优化出去 不会检查
// 取消检查操作 1,注释掉 2,宏取消 #define NDEBUG 取消assert 的指针检查 这个宏需要在 assert头文件的上面
//
//void test(int* pa)
//{
// // 对所传地址进行检查
// assert(pa);
//}
//int main()
//{
// int a = 10;
// int* pa = NULL;// 创建指针变量不知道赋什么值时 记得指向空 避免野指针的出现
//
// // 检查
// //assert(pa);// 为空则直接 程序崩溃
// // 如果是对未初始化的指针变量进行检查时 会直接报错,编译都过不去
//
// test(pa);
//
//
// return 0;
//}
// 指针传参 传值与传地址
//
//void test(int a, int b)
//{
// int tmp = a;
// a = b;
// b = tmp;
// printf("%d %d\n", a, b);
//}
//
//void test1(int* a, int* b)
//{
// // 只要是指针的变量 都可以使用 assert进行断言检查
// assert(a && b);
// int tmp = *a;
// *a = *b;
// *b = tmp;
//}
//int main()
//{
// int a = 1;
// int b = 2;
//
// // 调换 a与b a=2 b=1
//
// // 传值
// //test(a, b); // 其原因就是 传值调用 函数所接收的只是实参的一份临时拷贝的内容,所以形参的改变并不会影响实参
// printf("%d %d\n", a, b); // 未实现调换
//
// //传地址调换
// //test1(&a, &b);// 原因就是 通过原地址 进行数据交换
// //printf("%d %d\n", a, b);// 完成调换
//
// // 不使用第三方变量 实现交换
// //a = a + b;
// //b = a - b;
// //a = a - b;
// //printf("%d %d\n", a, b);// 数学逻辑 实现交换
//
// // 位运算实现交换
// a = a ^ b;
// b = a ^ b;
// a = b ^ a;
// // 01
// // 10
// // ^ 11 a
// // 11
// // 10
// // ^ 1 b
// // 01
// // 11
// // ^ 10 a
// //
// printf("%d %d\n", a, b); // 实现交换 速度比数学逻辑块 因为这是直接使用编程逻辑
//
// return 0;
//}
// sizeof 与 strlen
// sizeof 可以计算任何类型的数据 (操作符 ,只在乎所占内存空间的大小)
// strlen 只可以计算字符串 (函数 所在意的 仅仅是 \0 之前有多少个字符的个数)
//
//void test1(int* arr)
//{
// // 函数所接收到的是一个地址值 所以计算的是指针的大小 不是 4 就是 8 字节
// printf("%zd\n", sizeof(arr));// 4/8
//}
//
//void test2(char* c)
//{
// // 字符串 通过地址偏移量往后寻找 \0
// printf("%d\n", strlen(c));
//}
//int main()
//{
// // 不完全初始化 剩余元素默认初始化为 0
// int arr[10] = { 0 };
// char c[10] = "012345678";// 创建字符串数组时 需要给 \0 预留一个空间 ,不然在实际使用时 后果将是未知的
//
// //printf("%zd\n", sizeof(arr) / sizeof(int));
// //printf("%zd\n", sizeof(c) / sizeof(char));
// //printf("%zd\n", strlen(c));
//
// // 数组传参时 仅仅是元素地址 并非整个数组内容,通过地址的偏移量就可以找到后续的内容
// test1(arr);
// test2(c);
//
// return 0;
//}
// 一维数组传参
//
//void test1(int* arr)
//{
// for (int i = 0; i < 10; i++)
// {
// printf("%d ", *(arr + i));// 指针偏移量的方式向后访问
// }
// printf("\n");
//}
//
//void test2(int* arr)
//{
// printf("%p\n", arr);// 数组起始地址
// printf("%p\n", arr+1);// +1 越过整个数组的地址
//}
//int main()
//{
// int arr[10] = { 0 };
//
// // 所传数据 是数组的首元素地址 并非数组全部内容
// test1(arr);
//
// // 所传数据是整个数组的地址 +- 操作会造成指针越界
// test2(&arr);// 已属于 二维数组的层次
//
// return 0;
//}
//
//
冒泡排序思想
//
交换
//void swap(int* arr, int i, int j)
//{
// int tmp = arr[i];
// arr[i] = arr[j];
// arr[j] = tmp;
//}
//
//void bouble_sort(int* arr, int s)
//{
// // 检查
// assert(arr);
//
// for (int i = 0; i < s-1; i++)
// {
// int flag = 1;// 假设有序
//
// // 防止 j+1越界
// for (int j = 0; j < s - 1 - i; j++)
// {
// // 降序条件是否符合
// if (arr[j] > arr[j + 1])
// {
// // 符合则交换
// swap(arr, j, j + 1);
// flag = 0;
// }
// }
// if (flag)
// {
// return;
// }
// }
//}
//
//int main()
//{
// int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
//
// int s = sizeof(arr) / sizeof(int);
// bouble_sort(arr, s);
//
// for (int i = 0; i < s; i++)
// {
// printf("%d ", arr[i]);
// }
// printf("\n");// 行缓冲
// return 0;
//}
//
//
//int main() {
// char c[100] = { 0 };
// int i = 0;
// char x = 0;
// while (scanf(" %c", x) != EOF)// 变量输入数据 需要取地址才可以
// {
// c[i] = x;
// if (c[i] == '0')
// {
// break;
// }
// i++;
// }
//
// printf("%s\n", c);
//
// return 0;
//}
转换大小写字母函数
//#include<ctype.h>
//
//int main() {
// char s[100] = "0";
// // 输入带有空格的字符串不能使用 scanf ,scanf在读取字符时 会已空格为结束标志
// // scanf("%s", s);
//
// // 需要使用 gets
// gets(s);
//
// int i = 1;
// int j = 0;
// char s1[100] = { 0 };
// s1[j] = s[0];
//
// // 提取首字母
// while (s[i])
// {
// if (s[i] == ' ')
// {
// s1[++j] = s[++i];
// }
// i++;
// }
//
// // 判断大小写
// j = 0;
// while (s1[j])
// {
// if (islower(s1[j]))
// {
// s1[j] -= 32;
// }
// j++;
// }
//
// printf("%s\n", s1);
//
// return 0;
//}
//
// 练习题插曲
/* -------------------------------------- */
// 二级指针
// 用来存放一级指针的地址的 指针,被称为 二级指针
// 指针可以无限嵌套指针
直接改变的话 需要以 二级指针 接收
void test(int** p)
{
int* new = malloc(sizeof(int));// 申请4个字节内存并返回开辟的地址
if (new == NULL)
{
return;
}
*p = new;
}
int main()
{
char* pc = "abcd";// 常量字符串,存放在内存常量区,不可修改,
指针所指向的元素为首元素地址 并不是整个字符串内容,是通过指针的偏移量向后依次访问的
二级指针
char** pcc = &pc;// *pcc表示这是一个指针 所指向的类型为 char*类型
此时 pcc的左值为内存开辟个这个指针变量的自己的地址 ‘
pcc的右值里存放着pc的地址值
printf("%p\n", pc);
printf("%p\n", &pc);
printf("%p\n", pcc);
printf("%p\n", &pcc);
printf("%s\n", *pcc);
二级指针多用于数据结构 用来传参使用
int* pa = NULL;
直接进行赋值会出问题,会发现未改变
test(pa);
原因就是 指针变量也是变量 也拥有自己的地址 不可随意更改
test(&pa);
此时解引用发现 指针地址 依然是 空指针
*pa = 10;
printf("%d\n", *pa);
return 0;
}
// 指针数组
// 概念:存放指针的数组
// 数组:相同类型元素的集合
// 所以 指针数组里存放的元素 也必须是相同类型的指针
//
//int main()
//{
// char* a = "abcd";
// char* b = "bcdef";
// char* c = "asdf";
//
// // 指针数组
// //char* ac[] = { a,b,c };
//
// char** ac[] = { &a,&b,&c };
// // 数组每一个的元素是 char* 类型 所以数组类型为 char**
// // 因为存放的是一级指针 所以需要以 二级指针接收
//
// // 值得注意的是 计算数组字节大小时 是按照指针的字节计算的,而指针大小 不是 4 就是 8
// printf("%zd\n", sizeof(ac));// 12字节大小
//
// // 指针数组好处是 可以通过数组元素下标来找到元素地址
// /*for (int i = 0; i < 3; i++)
// {
// printf("%s\n", **(ac + i));
// }
//
// char** pa = &a;
// printf("%s\n", *pa);
// printf("%p\n", a);
// printf("%p\n", &a);
// printf("%p\n", ac);
// printf("%p\n", *ac);
// printf("%p\n", **ac);
// printf("%p\n", &ac);*/
//
// printf("%s\n", *ac);
//
// return 0;
//}
// 数组指针
// 指向数组的指针 一般用于二维及以上的多维数组
//
//int main()
//{
// int arr[] = { 1,2,3,4 };
//
// //数组指针
// int(*p)[4] = &arr;// 指针数组标准写法
// // 类型 int (*p)表示这是一个指针 【4】 指向一个,int类型元素个数为4 的数组
// // 注意!数组指针 指向的是整个数组的起始地址 所以需要对 数组名取地址
//
// // 多维数组传参时 只可以省略第一个维度 其余维度不可是省略
// //
//
// return 0;
//}
// 函数指针
// 函数也拥有自己的地址值 &函数名 与 函数名 等同
// 函数指针数组
// 存放函数指针的 数组
// 使用函数指针数组 实现一个简单的 计算器
加法
//int add(int x, int y)
//{
// return x + y;
//}
//
减法
//int sub(int x, int y)
//{
// return x - y;
//}
//
乘法
//int mul(int x, int y)
//{
// return x * y;
//}
//
除法
//int Div(int x, int y)
//{
// if (y == 0)
// {
// printf("erro\n");
// return -1;
// }
// return x / y;
//}
//
函数指针接收时的写法
//void test(int(*pf)(int, int))
//{
// printf("%d\n", pf(3, 4));
//}
//
函数指针重命名
//typedef int(*padd)(int, int);// 对名字直接修改即可
要注意的是 这是对类型改名
//
//int main()
//{
// // 命名前
// int(*p)(int, int) = add;
//
// // 命名后 此时padd就是 指针类型 int(*)(int,int)
// padd pa = add;
// //
// //printf("%d\n", pa(2, 3));// 函数指针与 函数用法相同
//
//
// // 函数指针传参 写法
// test(pa);
//
//
// 函数指针数组 (相同的 返回类型 参数)
// //int(*pf[])(int, int) = { add,sub,mul,Div };
// pf先和[] 结合 则pf是一个数组,去掉 pf[],就是数组的 类型
// 返回类型 (*) (参数)=函数 就是一个函数指针的基本写法
//
//
// //int a, b;
// //scanf("%d %d", &a, &b);// 4 2
//
// //for (int i = 0; i < 4; i++)
// //{
// // printf("%d ", pf[i](a, b));// 6 2 8 2
// //}
//
// return 0;
//}
// 使用冒泡排序 模拟实现 qsort
// 对 void* 的具体实操
//
//int int_sort(const void* p1, const void* p2)
//{
// // void* 不可直接解引用
// return *(int*)p1 - *(int*)p2;
//}
//
//void swap(char* p1, char* p2, int sz)
//{
// // 交换字节内容 不可以交换地址 不然其结果是 所指地址还是哪个地址值 任未交换
// for (int i = 0; i < sz; i++,p1++,p2++)
// {
// char tmp = *p1;
// *p1 = *p2;
// *p2 = tmp;
//
//
// }
//}
//
//void my_qsort(void* arr, int num, int sz, int(*pf)(void*, void*))
//{
// for (int i = 0; i < num - 1; i++)
// {
// int flag = 1;// 假设有序
// // -1 防止越界
// for (int j = 0; j < num - 1 - i; j++)
// {
// // 判断
// if (pf((char*)arr + j * sz, (char*)arr + (j + 1) * sz) > 0)
// {
// // 交换
// swap((char*)arr + j * sz, (char*)arr + (j + 1) * sz,sz);
// flag = 0;
// }
// }
//
// if (flag)
// {
// return;
// }
// }
//}
//
打印
//void print(int* arr, int sz)
//{
// for (int i = 0; i < sz; i++)
// {
// printf("%d ", arr[i]);
// }
// printf("\n");
//}
//
//int main()
//{
// // 创建数组
// int arr[10] ={1, 3, 5, 7, 9, 0, 2, 4, 8, 6};
// // 计算元素个数
// int sz = sizeof(arr) / sizeof(int);
//
// print(arr, sz);
//
// // 数组起始地址 待排元素个数 每个元素的字节大小 函数指比较大小
// my_qsort(arr, sz, sizeof(int), int_sort);
//
// print(arr, sz);
//
// return 0;
//}
至此 C指针学习完毕
1,什么是指针 指针干嘛用的
2,指针大小 指针类型 指针运算 sizeof 与 strlen
3,指针传参
4,指针数组 数组指针 代码书写规范
5,函数指针 函数指针数组 代码书写规范 typedef 对函数指针的重命名规范
6,const对指针的作用 assert断言的作用
7,预防野指针的出现
8,函数栈帧是如何创建和销毁的
9,数组名的理解 以及对地址所指向的地址清晰明了
10,指针非常重要 多巩固
// ....................................