C_指针日志,总结篇

地址是内存中唯一标识符!!!


// 指针的概念:指针即地址 ,地址即指针。通过地址就可以找到变量的内存空间 从而对空间进行一系列操作
// 

指针变量
//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,指针非常重要 多巩固
// ....................................

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值