C语言指针

什么是指针:

    1、指针是一种数据类型,使用它可以定义指针变量,简称指针。

    2、指针变量中存储的是内存地址(整数,便签序号),可以通过它访问对应的内存

    3、以32位系统为例,指针变量的需要占用4字节内存空间,取值范围是:0x00000000~0xffffffff,每个整数代表一个字节,最多可以访问4G内存(64位系统的指针变量则需要占用8字节内存空间)。

    4、可以使用printf+%p 显示指针变量的值。

如何使用指针:

    定义指针变量:类型* 指针变量名;

        1、指针变量与普通变量的使用方法不同,因此为区别与普通变量,一般指针变量以p结尾。

        2、指针变量不能连续定义,一个*只能定义出一个指针变量。

            int* p1,p2; //  p1是指针变量,p2普通的int类型变量

            int *p1,*p2; // p1,p2都是指针变量

        3、指针变量中存储的整数只代表一个字节,当通过指针变量访问内存时,具体访问多少个字节由指针变量的类型决定,也就是说指针变量中存储的是一块内存的首地址。

        4、指针变量的默认值与普通变量一样是随机的(野指针),一般为了安全要给指针变量初始化,如果没有合适的内存地址可以先初始化NULL(空指针)。

    给指针变量赋值:指针变量 = 内存地址;

        // 定义指针变量时初始化

        int* p = # // 获取变量的地址赋值给指针变量,num变量的类型要与指针变量相同,否则编译器会产生警告,在解引用时也可能产生段错误

        // 定义完指针变量后再赋值

        p = malloc(4); // 把堆内存的地址赋值给指针变量,堆内存的使用后续会详细讲解

    根据指针变量中存储的内存地址访问内存(解引用):*指针变量

        int num = 1234;

        int* p = #

        *p <=> num; // *p 等价于 num

    指针变量解引用时,具体访问多个少字节数由指针变量的类型决定:

            char* p; // *p 访问一个字节

            short* p; // *p 访问两个字节

            double* p; // *p 访问4个字节

    指针变量解引用时可能出现段错误的原因:

        1、指针变量中存储的是无效的内存地址(不在maps文件里的地址范围内)。

        2、指针变量中存储的是具有只读权限的内存地址,通过指针变量修改内存时也会产生段错误。

        const int num = 1234;   // num使用的是代码段内存

        int main()

        {

            int* p = (int*)&num;

            *p = 6666; // 强行修改只读权限的内存,会产生段错误

        }

        3、指针变量中存储的是NULL,解引用时肯定产生段错误。

        总结:当指针变量解引用时产生段错误,要在指针变量赋值时改正。

为什么要使用指针:

    1、使用指针可以在函数之间共享变量

        使用全局变量可以在函数之间共享变量,但使用全局变量容易造成命名冲突,并且在程序运行期间全局变量所使用的data、bss、text内存不会被释放,这多使用全局变量容易造成内存浪费,尽量少使用全局变量,最好不用。

        当函数需要返回多个执行结果时,需要在函数之间共享变量,也就是共享变量地址,例如:scanf函数。

    2、适当的使用指针可以提高函数的传参效率

        c语言的函数传参是值传递(赋值、内存拷贝),比如:double,long double,long long,自定义类型(结构、联合、类),它们的字节数 > 4,如果直接传递变量的值则最少需要拷贝8字节内存数据,而传递变量的地址,只需要拷贝4字节内存数据。

        // 以下代码传递变量的地址比直接传递变量的值要节约一半的时间

        void func(long double* p)

        {

        }

        int main()

        {

            long double f = 3.14;

            for(int i=0; i<2000000000; i++)

            {

                func(&f);

                f+=1;

            }

        }

    3、使用堆内存必须与指针变量配合

        当 int num; 语句时,系统会分配4字节内存(text、data、bss、stack),并让这4字节内存与变量名num建立联系,在之后的代码中使用num就相当于4字节内存,我们把这种功能叫做给内存取名字。

        而堆内存无法取名字,当向系统申请一块内存时,系统会返回这块内存的首地址,这块内存无法与变量名建立联系,也就是无法取名字,需要指针变量存储内存的地址以便之后使用这块堆内存,所以说堆内存必须与指针变量配合使用。

指针的运算:

    指针变量中存储的是代码内存地址的整数,理论所有整数能进行的运算,指针变量也能够运算,但只有以下三种运行对指针变量来说是有意义:

        注意:指针变量的进步值就是指针变量解引用时所访问的字节数。

        指针+n => 指针变量中的整数+进步值*n

        int* p = 0;

        p+3 得到的结果是12

        指针-n => 指针变量中的整数-进步值*n

        short* p = 8;

        p - 3 得到的结果2

        指针-指针 => (指针变量中的整数-指针变量中的整数)/进步值

        double *p1 = 36 ,*p2 = 20;

        p1 - p2 得到的结果是2

        指针变量加减整数相当于指针变量前后移动,指针减指针可以计算出两地址之间相隔多个对象(两个指针变量的类型必需相同)。

使用指针要注意的问题:

    空指针:值为NULL的指针变量叫空指针,系统规定不能对空指针解引用,否则就会产生段错误。

    如何避免空指针产生的段错误:对来历不明的指针解引用前,先判断是否是空指针。

        1、函数的返回值如果是指针类型,当函数执行出错、失败时会返回空指针。

        2、当函数的参数是指针时,我们无法保证调用者传递的指针是否是空指针,所以在解引用前要先判断。

        注意:大多数系统的NULL是0地址,但也有少数系统的NULL是1地址,所以判断空指针时

        if(!p) // 不通用,容易让人误会p是布尔类型

        {

        }

        if(p == NULL) // 当少写一个=时,编译器不会报错,就变成赋值NULL指针了,接下来肯定会产生段错误

        {

        }

        if(NULL == p) // 完美

        {

        }

    野指针:指针变量中存储的地址不知道是否"合法",这种指针叫野指针。

        对野指针解引用可能产生的后果:

            1、段错误

            2、脏数据

            3、一切正常

        无法判断一个指针变量是否是野指针,并且野指针产生的错误具有隐藏性、潜伏性、随机性,所以野指针比空指针的危害更大。

        如何避免野指针产生的危害:

            前提:所有野指针都是人为制造出来的,所以只要不制造野指针就能避免野指针产生的危害。

            1、定义指针变量时一定要初始化,宁可赋值为NULL,也不要产生野指针。

            2、函数不要返回局部变量的地址,因为随着函数的执行结束,属性局部变量的内存会被释放,,因此这种情况返回的地址就是野指针。

            3、当堆内存被释放后,与它配合的指针变量要及时赋值为NULL。

const与指针:

    const与指针配合使用一共有5种写法,3种功能:

    功能1:保护指针变量不被修改

        int* const p;

    功能2:保护指针变量指向的内存不被修改

        const int* p;

        int const * p;

    功能3:既保护指针变量也保护指向变量所指向的内存不被修改

        const int* const p;

        int const * const p;

指针与数组名:

    数组名:数组名就是个地址,所以数组作为函数的参数时才能蜕变成指针,它是数组内存块的首地址,也就是第一个元素的内存首地址,所以数组名可以使用解引用的方式访问数组中的每个元素。

    int arr[] = {1,2,3,4,5}; // arr就是int*类型的地址

    for(int i=0; i<5; i++)

    {

        printf("%d ",*(arr+i));

    }

    *(arr+i) <=> arr[i] 这两种写法是等价的。

    数组名可以使用指针的语法,指针也可以使用数组名的语法。

    int arr[] = {1,2,3,4,5}; // arr就是int*类型的地址

    int* p = arr;

    for(int i=0; i<5; i++)

    {

        printf("%d ",p[i]);

    }

    数组名与指针的相同点:

        1、它们都是地址,代表着一块内存的首地址。

        2、它们都可以使用 *、[] 访问内存中的数据。

    数组名与指针的不同点:

        1、数组名是常量,而指针是变量。

        2、指针变量有4字节的存储空间,而数组名就是地址。

            arr == &arr // 地址相同,但类型不同

        3、指针与它指向的内存之间是指向关系,数组名与内存之间是映射关系,数组名会替换成地址。

通用指针:

    通用指针就是void类型的指针,它能与任意类型的指针进行转换,缺点是不能解引用,必须先转换成其它类型的指针才能解引用,进步值是1。

    为什么使用通用指针:

        不同类型的指针不能互相赋值(因为它们解引用时所访问的内存字节数不同),编译器会产生警告。

        但不同类型的指针和数组会有一些通过操作,比如:清理内存、内存拷贝、数组排序等,当实现这类功能的函数时调用者可能提供任意类型指针或数组,这种情况就需要使用void类型的指针作为函数的参数。

指针函数:

    返回值是指针类型的函数,没有什么需要重点关注,主要为了防止别人装13。

函数指针:

    前提:一个函数就是一段代码,它会被翻译成二进制指令,存储在text内存段中,函数名就是它在text内存的首地址,我们只要知道函数的地址和函数的类型就能调用这个函数。

    函数指针:专门存储函数地址的指针变量。

    如何定义函数指针:

        1、照抄函数的声明。

        2、用小括号包括一个函数名。

        3、在函数名前面加*,末尾加fp,防止命名冲突。

    如何使用函数指针:

        1、用函数名给函数指针赋值。

        2、函数指针() 就可以调用函数。

    函数指针有什么用:

        可以把函数像数据一样在函数之间传递,这样可以做到让旧函数调用新函数,这种函数的调用模式叫回调模式,例如:qsort函数。

    void qsort(void *base, size_t nmemb, size_t size,

                  int (*compar)(const void *, const void *));

    1、在设计sqort函数时预留一个函数指针的参数compar,在qsort函数内会用compar调用函数,用于比较数组中的每个元素。

    2、当调用qsort函数时,调用者需要提数组首地址、数组长度、每个元素的字节数,根据这三个参数把数组以size字节为单位切割成nmemb个元素,然后调用compar对这些元素进行比较,根据比较的结果对数组进行排序。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,701评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,649评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,037评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,994评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,018评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,796评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,481评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,370评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,868评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,014评论 3 338
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,153评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,832评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,494评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,039评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,156评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,437评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,131评论 2 356

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,446评论 3 44
  • 1.语言中变量的实质 要理解C指针,我认为一定要理解C中“变量”的存储实质, 所以我就从“变量”这个东西开始讲起吧...
    金巴多阅读 1,781评论 0 9
  • 这里对 C 语言的指针进行比较详细的整理总结,参考网络上部分资料整理如下,希望能对大家有所帮助。 指针概念 计算机...
    Veeupup阅读 243评论 0 0
  • 前言:复杂类型说明 要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解...
    mingguang阅读 184评论 0 0
  • 1. 变量 不同类型的变量在内存中占据不同的字节空间。 内存中存储数据的最小基本单位是字节,每一个字节都有一个内存...
    C语言学习阅读 1,283评论 0 4