• 指针


    取地址运算

    1 . 运算符 &

    • scanf(“%d”, &i);⾥的&
    • 获得变量的地址,它的操作数必须是变量,int i; printf(“%x”,&i);
    • 地址的⼤⼩是否与int相同取决于编译器,int i; printf(“%p”,&i);

    2. &不能取的地址

    &不能对没有地址的东⻄取地址

    &(a+b)?

    &(a++)?

    &(++a)?

    3. 试试这些&

    变量的地址

    相邻的变量的地址

    &的结果的sizeof

    数组的地址

    数组单元的地址

    相邻的数组单元的地址

    指针

    1. scanf

    如果能够将取得的变量的地址传递给⼀个函数, 能否通过这个地址在那个函数内访问这个变量?

    • scanf(“%d”, &i);

    scanf()的原型应该是怎样的?我们需要⼀个参数 能保存别的变量的地址,如何表达能够保存地址 的变量?

    2. 指针

    就是保存地址的变量

    int i;

    int* p = &i;

    int* p,q;

    int *p,q;

    3. 指针变量

    • 变量的值是内存的地址
    1. 普通变量的值是实际的值
    2. 指针变量的值是具有实际值的变量的地址

    4. 作为参数的指针

    • void f(int *p);
    • 在被调⽤的时候得到了某个变量的地址:int i=0; f(&i);
    • 在函数⾥⾯可以通过这个指针访问外⾯的这个i

    5. 访问那个地址上的变量*

    • *是⼀个单⺫运算符,⽤来访问指针的值所表⽰的地 址上的变量
    • 可以做右值也可以做左值int k = *p;*p = k+1;

    6. *左值之所以叫左值

    • 是因为出现在赋值号左边的不是变量,⽽是值,是表 达式计算的结果:a[0] = 2;   *p = 3;
    • 是特殊的值,所以叫做左值

    7. 指针的运算符&*

    互相反作⽤

    *&yptr -> * (&yptr) -> * (yptr的地 址)-> 得到那个地址上的变量 -> yptr

    &*yptr -> &(*yptr) -> &(y) -> 得到y的地 址,也就是yptr -> yptr

    8. 传⼊地址

    • 为什么 ? int i; scanf(“%d”, i);
    • 编译没有报错?

    9.  指针应⽤场景⼀

    交换两个变量的值

    void swap(int *pa, int *pb){
        int t = *pa;
        *pa = *pb;
        *pb = t;
    }

    10. 指针应⽤场景⼆

    • 函数返回多个值,某些值就只能通过指针返回
    • 传⼊的参数实际上是需要保存带回的结果的变量

    11. 指针应⽤场景⼆b

    • 函数返回运算的状态,结果通过指针返回
    • 常⽤的套路是让函数返回特殊的不属于有效范围内的 值来表⽰出错:-1或0(在⽂件操作会看到⼤量的例⼦)
    • 但是当任何数值都是有效的可能结果时,就得分开返 回了,后续的语⾔(C++,Java)采⽤了异常机制来解决这 个问题

    12. 指针最常⻅的错误

    • 定义了指针变量,还没有指向任何变量,就开始使⽤ 指针

    13. 9.1-3 指针与数组

    传⼊函数的数组成了什么?

    • 函数参数表中的数组实际上是指针
    1. sizeof(a) == sizeof(int*)
    2. 但是可以⽤数组的运算符[]进⾏运算

    14. 数组参数

    以下四种函数原型是等价的:

    int sum(int *ar, int n);

    int sum(int *, int);

    int sum(int ar[], int n);

    int sum(int [], int);

    15.数组变量是特殊的指针

    • 数组变量本⾝表达地址,所以
    1. int a[10]; int*p=a; // ⽆需⽤&取地址
    2. 但是数组的单元表达的是变量,需要⽤&取地址
    3. a == &a[0]
    • []运算符可以对数组做,也可以对指针做:
    1. p[0] <==> a[0]
    • *运算符可以对指针做,也可以对数组做:
    1. *a = 25;
    • 数组变量是const的指针,所以不能被赋值
    1. int a[] <==> int * const a=….

    *9.1-4 指针与const

    1.指针与const

    2. 指针是const

    • 表⽰⼀旦得到了某个变量的地址,不能再指向 其他变量
    1. int * const q = &i; // q 是 const
    2. *q = 26; // OK
    3. q++; // ERROR

    3. 所指是const

    • 表⽰不能通过这个指针去修改那个变量(并不 能使得那个变量成为const)
    1. const int *p = &i;
    2. *p = 26; // ERROR! (*p) 是 const
    3. i = 26; //OK
    4. p = &j; //OK

    4. 这些是啥意思?

    int i;

    const int* p1 = &i;

    int const* p2 = &i;

    int *const p3 = &i;

    判断哪个被const了的标志是const在*的前⾯还是后⾯

    5. 转换

    • 总是可以把⼀个⾮const的值转换成const的

    void f(const int* x);

    int a = 15; f(&a);// ok

    const int b = a;

    f(&b); // ok

    b = a + 1; // Error!

    • 当要传递的参数的类型⽐地址⼤的时候,这是常⽤的⼿ 段:既能⽤⽐较少的字节数传递值给参数,⼜能避免函 数对外⾯的变量的修改

    6. const数组

    const int a[] = {1,2,3,4,5,6,};

    数组变量已经是const的指针了,这⾥的const 表明数组的每个单元都是const int

    所以必须通过初始化进⾏赋值

    7. 保护数组值

    因为把数组传⼊函数时传递的是地址,所以那个函数 内部可以修改数组的值

    为了保护数组不被函数破坏,可以设置参数为const

    • int sum(const int a[], int length);

    指针是可计算的

    1. 1+1=2?

    • 给⼀个指针加1表⽰要让指针指向下⼀个 变量
    1. int a[10];
    2. int *p = a;
    3. *(p+1) —> a[1]
    • 如果指针不是指向⼀⽚连续分配的空间, 如数组,则这种运算没有意义

    2. 指针计算

    • 这些算术运算可以对指针做:
    1. 给指针加、减⼀个整数(+, +=, -, -=)
    2. 递增递减(++/—)
    3. 两个指针相减

    3. *p++

    • 取出p所指的那个数据来,完事之后顺便 把p移到下⼀个位置去
    • *的优先级虽然⾼,但是没有++⾼
    • 常⽤于数组类的连续空间操作
    • 在某些CPU上,这可以直接被翻译成⼀条 汇编指令

    4. 指针⽐较

    • <, <=, ==, >, >=, != 都可以对指针做
    • ⽐较它们在内存中的地址
    • 数组中的单元的地址肯定是线性递增的

    5. 0地址

    • 当然你的内存中有0地址,但是0地址通常是 个不能随便碰的地址
    • 所以你的指针不应该具有0值
    • 因此可以⽤0地址来表⽰特殊的事情:
    1. 返回的指针是⽆效的
    2. 指针没有被真正初始化(先初始化为0)
    • NULL是⼀个预定定义的符号,表⽰0地址
    1. 有的编译器不愿意你⽤0来表⽰0地址

    6. 指针的类型

    • ⽆论指向什么类型,所有的指针的⼤⼩都 是⼀样的,因为都是地址
    • 但是指向不同类型的指针是不能直接互相 赋值的
    • 这是为了避免⽤错指针

    7. 指针的类型转换

    • void* 表⽰不知道指向什么东⻄的指针
    1. 计算时与char*相同(但不相通)
    • 指针也可以转换类型
    1. int *p = &i; void*q = (void*)p;
    • 这并没有改变p所指的变量的类型,⽽是让 后⼈⽤不同的眼光通过p看它所指的变量
    1. 我不再当你是int啦,我认为你就是个void!

    8. ⽤指针来做什么

    • 需要传⼊较⼤的数据时⽤作参数
    • 传⼊数组后对数组做操作
    • 函数返回不⽌⼀个结果
    1. 需要⽤函数来修改不⽌⼀个变量
    • 动态申请的内存...

    动态内存分配

    1.输⼊数据

    • 如果输⼊数据时,先告诉你个数,然后再输⼊,要记 录每个数据
    • C99可以⽤变量做数组定义的⼤⼩,C99之前呢?
    • int *a = (int*)malloc(n*sizeof(int));

    2. malloc

    • #include void* malloc(size_t size);
    • 向malloc申请的空间的⼤⼩是以字节为单位 的
    • 返回的结果是void*,需要类型转换为⾃⼰ 需要的类型
    1. (int*)malloc(n*sizeof(int))

    3.没空间了?

    如果申请失败则返回0,或者叫做NULL 

    你的系统能给你多⼤的空间?

    4. free()

    • 把申请得来的空间还给“系统”
    • 申请过的空间,最终都应该要还
    1. 混出来的,迟早都是要还的
    • 只能还申请来的空间的⾸地址 • free(0)?

    5. 常⻅问题

    • 申请了没free—>⻓时间运⾏内存逐渐下降
    1. 新⼿:忘了
    2. ⽼⼿:找不到合适的free的时机
    • free过了再free
    • 地址变过了,直接去free

    函数间传递指针

    1. 好的模式

    如果程序中要⽤到动态分配的内存,并且 会在函数之间传递,不要让函数申请内存 后返回给调⽤者

    因为⼗有⼋九调⽤者会忘了free,或找不 到合适的时机来free

    好的模式是让调⽤者⾃⼰申请,传地址进 函数,函数再返回这个地址出来

    2. 函数返回指针?

    • 返回指针没问题,关键是谁的地址?
    1. 本地变量(包括参数)?函数离开后这些变量就不 存在了,指针所指的是不能⽤的内存
    2. 传⼊的指针? 没问题
    3. 动态申请的内存? 没问题
    4. 全局变量—>以后再解释

    函数返回数组?

    • 如果⼀个函数的返回类型是数组,那么它实际返回 的也是数组的地址
    • 如果这个数组是这个函数的本地变量,那么回到调 ⽤函数那⾥,这个数组就不存在了
    • 所以只能返回
    1. 传⼊的参数:实际就是在调⽤者那⾥
    2. 全局变量或动态分配的内存
  • 相关阅读:
    开博的缘由
    听了一节公开课 课后感
    python中的函数、变量和递归函数
    迭代器和生成器
    字符串的格式化
    html和htm的区别
    基本数据类型总结
    数据类型-----集合
    基础测试题(字符串、列表、元组、字典)
    基本数据类型-----字典(Dictionary)
  • 原文地址:https://www.cnblogs.com/hechunfeng/p/15659644.html
Copyright © 2020-2023  润新知