zoukankan      html  css  js  c++  java
  • 指针

    内存和地址

    内存其实就是一组有序字节组成的数组,数组中,每个字节大小固定,都是 8bit。对这些连续的字节从 0 开始进行编号,每个字节都有唯一的一个编号,这个编号就是内存地址。示意如下图:

    指针变量保存的就是这些编号,也即内存地址。

    地址与内容

    我们只要知道内存地址,就可以访问这个地址的值,但是这种方法实在笨拙,于是便用变量名来代替地址:

    名字与内存之间的关联仅仅只是编译器实现的,采用变量名的方式能够方便的记住地址,但是硬件仍然通过地址访问内存位置。

    值和类型

    考虑下面的32位值:

    ‭01100111011011000110111101100010‬
    

    对于这些位的解释可以分为很多种:

    类型
    1个32位数 1735159650
    2个16位数 26476和28514
    4个字符 glob
    浮点数 1.116533e24
    机器指令 beg.+110和ble.+102

    可见:不能简单地通过检查一个值的位来判断值的类型,而应该根据它的使用方式来判断

    使用指针的优势

    在C语言中,指针的使用非常广泛,因为使用指针往往可以生成更高效、更紧凑的代码。总的来说,使用指针有如下好处:

    • 指针的使用使得不同区域的代码可以轻易的共享内存数据,这样可以使程序更为快速高效。
    • C语言中一些复杂的数据结构往往需要使用指针来构建,如链表、二叉树等。
    • C语言是传值调用,而有些操作传值调用是无法完成的,如通过被调函数修改调用函数的对象,但是这种操作可以由指针来完成,而且并不违背传值调用。

    取变量地址间接访问

    对于一个操作数取地址使用单目操作符&

    通过指针访问它所指向的地址的过程称为间接访问或解引用指针,执行间接访问的操作符是单目操作符*

    声明一个指针

    未初始化和非法指针

    声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前,指针必须进行初始化:

    • 使它指向现有的内存
    • 给它动态分配内存

    指针的初始化实际上就是给指针一个合法的地址,让程序能够清楚地知道指针指向哪儿:

    /* 方法1:使指针指向现有的内存 */
    int x = 1;
    int *p = &x;  // 指针 p 被初始化,指向变量 x ,其中取地址符 & 用于产生操作数内存地址
    
    /* 方法2:动态分配内存给指针 */
    int *p;
    p = (int *)malloc(sizeof(int) * 10);    // malloc 函数用于动态分配内存
    free(p);    // free 函数用于释放一块已经分配的内存,常与 malloc 函数一起使用
    

    果一个指针没有被初始化,那么程序就不知道它指向哪里。它可能指向一个非法地址,这时,程序会报错,在 Linux 上,错误类型是 Segmentation fault(core dumped)提醒我们段违例或内存错误。它也可能指向一个合法地址,实际上,这种情况更严重,你的程序或许能正常运行,但是这个没有被初始化的指针所指向的那个位置的值将会被修改,而你并无意去修改它。

    NULL指针

    NULL 指针是一个特殊的指针变量,表示不指向任何东西。可以通过给一个指针赋一个零值来生成一个 NULL 指针。

    对一个NULL指针解引用操作是非法的,在对指针解引用之前必须确保它并非是NULL指针

    指针的运算

    算术运算

    C 指针的算术运算只限于两种形式:指针 +/- 整数 ,指针 - 指针。

    指针 +/- 整数

    可以对指针变量 p 进行 p++、p--、p + i 等操作,所得结果也是一个指针,只是指针所指向的内存地址相比于 p 所指的内存地址前进或者后退了 i 个操作数。

    指针 - 指针

    只有当两个指针都指向同一个数组中的元素时,才允许从一个指针减去另一个指针。

    两个指针相减的结果的类型是 ptrdiff_t,它是一种有符号整数类型。

    减法运算的值是两个指针在内存中的距离(以数组元素的长度为单位,而不是以字节为单位),因为减法运算的结果将除以数组元素类型的长度。

    关系运算

    使用操作符<,<=,>,>=对两个指针比较的前提是两个指针指向同一个数组中的元素,比较的结果是哪个指针指向数组更前或者更后的元素。

    标准没有定义两个任意指针之间的比较会发生什么。

    任意两个指针之间可以使用操作符!=、==进行比较,判断它们是不是指向同一个地址。

    指针表达式

    假设:

    char ch = 'a';
    char* cp = &ch;
    

    那么:

    表达式 含义
    *cp 作为左值,表示cp指向的地址,也就是变量ch的地址;作为右值,表示cp所指向地址存放的内容,也就是'a'
    *cp + 1 作为右值,(*cp + 1)表示存放的内容加1,就是字符'a'+1,得到字符'b';作为左值,*cp作为左值的意思是cp指向的那个地址本身,由于*cp + 1 这个表达式的最终结果的存储位置并未清晰定义,所以它不是一个合法的左值。
    *(cp + 1) 作为右值,访问的是cp指向的位置的下一个位置的值;作为左值的话,就是cp+1 这个指针对应的位置本身。
    ++cp 表达式增加了指针变量cp的值。表达式的结果是增值后的指针的一份拷贝,因为前缀++先增加它的操作数的值再返回这个结果。所以作为右值而言, 它是ch的地址值加1的地址值。同样由于该位置未清晰定义,故而不能作为左值。
    cp++ 作为右值使用的话,它表示cp本身的值,也就是ch的地址值;如果cp++作为左值,会对cp进行加1操作后在被赋值,由于cp++代表的地址未有清晰的定义,所以不能作为左值。
    *++cp 相当于*(++cp),作为右值,cp作为指针变量的值加1后,也就是ch的地址值加1,得到一个新的地址,加上间接访问符之后,便是取该地址对应的内存中的内容;作为左值,显然是cp +1指向的那个地址本身,也即是ch的地址加1后的那个新地址本身。
    *cp++ 相当于*(cp++),作为右值;是取cp指向的地址(即ch代表的地址&ch)的内容。这里也就是字符'a';作为左值,这个表达式就表示cp指向的地址本身,也即是ch的地址。
    ++*cp 相当于++(*cp),作为右值的,*cp的意思是去cp指向的地址的值,这里为'a',然后执行++操作,也就是加1,那么表达式的值为字符'b'*cp作为左值的意思是cp指向的那个地址本身,也即是ch代表的地址,再进行++操作,也就是地址加1,得到的新地址未清晰定义,所以不能作为左值。
    (*cp)++ 作为右值,就是先取cp指向的地址的值,然后++,这样得到的就是'b',作为左值,显然是非法的,因为最后执行的是++操作。或者说,*cp作为左值,代表cp指向的地址本身,再进行++,得到的是一个未清晰定义的地址,不能作为左值。
    ++*++cp 等价形式++(*(++cp)),由于最后进行的是++操作,所以不能作为左值;作为右值的情况,先进行++cp操作,是cp的地址加1得到的一个新地址(&ch + 1),之后进行间接访问操作,取新地址对应的内存存储的的值,再次进行++,是对这个新地址对应的内存中的值加1
    ++*cp++ 等价形式++(*(cp++)),同样,不能作为左值使用,因为最后操作的是++;作为右值的话,cp++中使用的是后缀++,故参与表达式运算的是cp的一份拷贝,之后再进行cp1操作,*(cp++)cp指向的地址的内容,即'a',之后对'a'加1得到'b'

    指针的高级声明

    指针的指针

    int i;
    int *pi = &i;
    int **ppi = &pi;
    

    数组指针

    数组指针是一个指针,它指向一个数组。

    int (*p)[10];        // 声明一个数组指针 p ,该指针指向一个数组,这个数组包含10个整型数
    

    函数指针

    int (*f)();			// f是函数指针,指向的函数返回一个int类型
    int (*f[])();		// f是一个数组,数组元素的类型是函数指针,它所指向的函数返回一个int类型
    int *(*g[])(int,float)	// g是一个数组,数组的元素是函数指针,它所指向的函数包含int、float参数,返回int*
    

    声明一个函数指针并不意味着可以马上使用,和其它类型的指针一样,对函数指针执行间接访问之前必须把它初始化为指向某个函数。

    例如:

    int (*pf)(int) = &f;
    

    &操作符是可选的,因为函数名被使用时总是由编译器把它转换成函数指针。

    &只是显示的地说明了编译器隐式执行的任务。

    函数调用的方式:

    int ans;
    ans = f(1);
    ans = (*pf)(1); //把函数指针解引用为函数名,这个转换起始并非真正需要,编译器还是会将函数名转换成函数指针
    ans = pf(1);
    
    

    函数指针应用

    回调函数

    用户把一个函数指针作为参数传递给其它函数,后者将回调用户传递进来的函数,这种技巧称为回调函数。

    当我们在在链表中查找一个数时,我们一般会这样写:

    Node *search_list( Node *node, int const value )
    {
        while ( NULL != node ){
            if ( node->value == value ){
                break;
            }
            node = node->link;
        }
    
        return node;
    }
    
    

    这样就限制我们只能在查找的数必须是int类型,当变为其他类型时我们就无法用这个函数,但是重新写一个函数,重复代码又太多。

    回调实现:

    int compare_int( void const *a, void const *b )
    {
        if ( *( int * )a == *( int * )b ){
            return 0;
        }
    
        return 1;
    }
    
    Node *search_list(Node *node, void const *value, 
        int (*compare)(void const *, void const *))  //函数指针
    {
        while(node != NULL){
            if(compare(&node->value, value) == 0)  //相等
                break;
            node = node->link;
        }
        return node;
    }
    
    

    这样利用回调函数就可以解决如上问题。我们把一个函数指针( int (*compare)(void const *, void const*) )作为参数传递给查找函数,查找函数将“回调”比较函数。当我们需要执行不同类型的比较时我们合理调用该函数。

    转移表

    假设有程序:

    switch (oper)
    {
    case ADD:	
    	result = add(op1, op2);
    	break;
    case SUB:
    	result = sub(op1, op2);
    	break;
    case MUL:
    	result = mul(op1, op2);
    	break;
    case DIV:
    	result = div(op1, op2);
    	break;
    ......
    }
    
    

    switch语句很长,所以,在这里用转移表来使问题得以简化。

    声明并初始化一个函数指针数组:

    double add(double, double);
    double sub(double, double);
    double mul(double, double);
    double div(double, double);
    ......
    
    // 声明一个函数指针数组
    double (*oper_func[])(double, double) = {
    	add, sub, mul, div,......
    };
    
    // 函数调用
    result = oper_func[oper](op1, op2);
    
    

    这样就可以将具体操作和选择操作的代码分开。

    命令行参数

    int main(int argc,char *argv[])
    int main(int argc,char **argv)
    
    
    argc : 命令行传入参数的总个数 
    argv : *argv[]是一个指针数组,里面存放的指针指向所有的命令行参数,argv[0]指向程序的名称,argv[1]指向在执行程序名后的第一个字符串,argv[2]指向第二个。
    
    

    字符串常量

    字符串常量的本质类型是指向字符的指针。

    "xyz"+1   // 结果是一个指针,指向'y'
    *"xyz"    // 结果是'x'
    "xyz"[2]  // 结果是'z'
    
    
  • 相关阅读:
    大话串口:我和它的恩恩怨怨
    分布式网游server的一些想法(一) 语言和平台的选择
    C++: C没有闭包真的很痛苦……
    C++不是C/C++
    最美树算法
    类魔兽世界 技能 天赋 成就 log 系统设计
    C++网游服务端开发(一):又无奈的重复造了个轮子,一个底层网络库
    C++ protobuf 不仅仅是序列化……
    深入WPFStyle
    Illusion = Caliburn.Micro + MEF
  • 原文地址:https://www.cnblogs.com/chay/p/11521315.html
Copyright © 2011-2022 走看看