首页 > 其他分享 >06. C语言指针

06. C语言指针

时间:2024-05-05 18:22:43浏览次数:20  
标签:p2 p1 06 int C语言 调用 数组 指针


【指针】

C语言使用数据名调用数据,数据名相当于C语言的直接寻址,直接寻址只能调用固定数据,而指针是间接寻址,指针存储了另一个数据的地址,使用指针调用数据时首先取指针存储的内存地址,之后使用此地址调用数据,使用间接寻址有如下几点优势:
1.统一数据的调用方式,因为指针是调用数据的中间层,修改指针即可调用不同的数据,当代码需要在不同情况下调用不同数据时,可以使用指针统一调用,只需要在不同情况下修改指针为不同的内存地址即可,无需编写多组代码分别调用这些数据,当数据需要在多处代码中同时调用、并在不同情况下同时修改时,使用指针的优势更明显,修改了指针就等于修改了所有使用指针调用数据的代码。
2.突破编译器限制,使用指针调用数据时编译器不会进行限制,只要操作系统不禁止即可,可以通过此特性绕过编译器的某些限制,比如在一个函数内调用另一个函数的局部数据。
3.接收未知的内存地址,比如向操作系统申请内存,比如调用动态链接库成员。

指针的长度在不同计算机中是不同的,在32位处理器程序中指针是无符号4字节整数,在64位处理器程序中指针是无符号8字节整数。
依据指向数据的类型可以将指针分为多种,指针用于存储哪种类型数据的地址就属于哪种类型的指针,编译器通过指针类型确定要操作多少个内存单元,int类型指针操作4个内存单元。

#include <stdio.h>
int main()
{
    int a = 9;
    int * p1 = &a;    //int指定指针类型,*符号表示定义指针,p1为指针名,使用&符号提取一个数据的地址进行赋值,注意这里的&符号并非表示与运算
    
    printf("变量a的值为:%d\n", *p1);     //使用 *p1 调用指针指向的数据
    printf("变量a的地址为:%p\n", p1);    //使用 p1 调用指针本身
    
    p1 = 0;         //指针不再使用后修改为0,避免错误调用
    
    return 0;
}

使用指针可以随意调用数据,数据调用方式更灵活,灵活的代价是容易出错,使用指针时应该做到代码严谨,同时定义指针暂时不使用时应该将其赋值为0,防止直接使用未赋值的指针,若指针占用的内存原有数据可以当做合规的内存地址使用,将会使用一个未知的内存地址,另外指针不再使用后也应该修改为0。

多重指针

指针也可以存储另一个指针的地址,相当于多层间接寻址。

#include <stdio.h>
int main()
{
    int a = 5;
    int * p1 = &a;      //指针
    int ** p2 = &p1;    //指针的指针
    int *** p3 = &p2;   //三重指针,很少使用
    
    printf("p2存储的数据为:%p\n"
        "p2指向的数据为:%p\n"
        "p2最终指向的数据为:%d\n",
        p2, *p2, **p2);
    
    return 0;
}

指针变量、指针常量

1.指针变量,指针本身是变量,赋值后可以修改,可以存储变量、常量的地址。
2.指针常量,指针本身是常量,赋值后不能修改,可以存储变量、常量的地址。
3.变量指针,指向变量的指针,存储变量的地址,可以通过指针修改指向的数据,不能存储常量的地址,但编译器默认只是给一个警告,不会禁止。
4.常量指针,指向常量的指针,存储常量的地址,不能通过指针修改指向的数据,也可以存储变量的地址,此时指针将变量认为是常量,并且不能通过指针修改它,若一个指针即需要指向变量也需要指向常量、同时不需要修改指向的数据,可以将其定义为常量指针。

#include <stdio.h>
int main()
{
    int a = 0;
    const int b = 9;
    
    int * const p1 = &a;    //const在指针名之前,定义指针常量
    const int * p2 = &b;    //const在类型名之前(或类型名与*符号之间),定义常量指针
    
    const int * const p3 = &b;    //组合使用
    
    return 0;
}

指针作为数组元素

#include <stdio.h>
int main()
{
    int i1=1, i2=2, i3=3, i4=4, i5=5;
    int * p1[5] = {&i1, &i2, &i3, &i4, &i5};    //p1为存储指针的数组,元素为int类型指针
    printf("%d\n", *p1[0]);                     //调用数组第一个元素指向的数据
    
    char * p2[2] = {"阿狸", "喜羊羊"};     //可以使用数组为p2赋值,编译器自动取每个数组的地址作为p2元素的值
    printf("%s\n%s\n", p2[0], p2[1]);    //输出p2两个元素指向的字符串
    
    return 0;
}

 ● restrict关键词

定义指针时添加restrict关键词表示代码可以保证指针指向的数据不会通过其他指针或者数据名进行修改,只会通过此指针修改,让编译器放心进行高效优化。

int * restrict p1;

 


【数组指针】

数组指针表示存储数组地址的指针,数组第一个元素的地址就是数组的地址,可以使用数组首元素的指针表示数组指针。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    int * p1 = &a[0];
    
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", *p1);
        p1++;                   //指针加1,指针记录的地址增加数据类型长度,这里的int类型指针会+4,定位到下一个元素
    }
    
    return 0;
}


数组指针可以使用下标的方式调用数组元素,原理与使用数组名相同,“指针地址+下标*数组元素长度”得出数组元素地址。

#include <stdio.h>
int main()
{
    /* 单层指针 */
    int a[5] = {1,2,3,4,5};
    int * p1 = &a[0];
    
    /* 遍历数组 */
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", p1[i]);    //将指针当做数组名使用,使用下标的方式调用数组元素
    }
    
    return 0;
}


数组指针还有另一种定义方式,它记录了数组长度信息,用于对指针赋值时进行检查,若赋值为长度不同的数组的地址则会发出警告,但默认不会禁止编译,这个长度信息只在编译器中记录,编译后的程序并没有记录此信息。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    int (*p1)[5] = &a;         //p1为数组指针的名称,只能赋值为5个int类型元素数组的地址
    
    printf("数组第一个元素的值:%d\n", (*p1)[0]);    //调用指针指向的数据
    printf("数组第一个元素的地址:%p\n", p1);        //调用指针存储的地址
    
    return 0;
}

 


【结构体指针】

#include <stdio.h>
int main()
{
    struct zoo
    {
        char name[20];
        int age;
    } ali = {"阿狸", 8};
    
    struct zoo * p1 = &ali;    //定义结构体指针,只能赋值为同类型结构体实例的地址
    
    printf("%s:%d岁\n", p1->name, p1->age);        //使用->符号调用结构体成员
    printf("%s:%d岁\n", (*p1).name, (*p1).age);    //另一种使用方式
    
    return 0;
}

共用体指针使用方式与结构体指针相同,不再重复介绍。

结构体成员指针

#include <stdio.h>
int main()
{
    struct zoo
    {
        char name[20];
        int age;
    } ali = {"阿狸", 8};
    
    char * p1 = &ali.name[0];
    int * p2 = &ali.age;
    
    printf("%s:%d岁\n", p1, *p2);
    
    return 0;
}

 


【指针运算】

指针变量可以进行数学运算,不同类型的指针运算结果不同,运算规则如下:
1.单数据指针,指针+1,指针本身增加数据的长度,定位到下一个同类型数据,比如int类型指针+1等于指针值+4,减法同理。
2.数组指针,指针+1,指针本身增加数组元素的长度,定位到数组下一个元素。
3.结构体指针,指针+1,指针本身增加结构体的长度,注意结构体长度需要额外计入成员地址对齐占用的存储空间。
4.函数指针,不支持数学运算。

#include <stdio.h>
int main()
{
    int a[5] = {1,2,3,4,5};
    int * p1 = &a[0];
    
    /* 遍历数组 */
    for(int i = 0; i < 5; i++)
    {
        printf("%d\n", *p1);    //输出
        p1++;                   //每次循环指针+1,定位到下一个元素
    }
    
    return 0;
}

数组元素指针相减

指向同一个数组不同元素的指针之间可以进行减法运算,编译器会转换为计算两个指针指向元素的下标相减。

#include <stdio.h>
int main()
{
	int a[10] = {0,1,2,3,4,5,6,7,8,9};
	int * p1 = &a[5];
	int * p2 = &a[8];
	
	printf("%lu\n", p2 - p1);    //p2-p1 = 8-5,输出3
	
	return 0;
}

 


【指针类型转换】

指针类型可以使用代码转换,编译器使用转换后的类型操作内存地址、进行指针运算,转换语法如下:(类型名*)指针名。
指针类型转换只存在于转换运算式中,不影响指针原来的类型,若需永久转换类型可以使用另一个指针接收转换后的指针。

指针类型可以随意转换,编译器不做限制,若一个未知数据需要通过指针逐个操作其中每个字节,可以将指针转换为char类型,之后即可按字节读写其中的数据。

#include <stdio.h>
int main()
{
    int a = 257;
    int *p1 = &a;
    printf("%d\n", *(char*)p1);    //int指针转换为char类型,只调用第一个字节,257转二进制 = 0001 0000 0001,低8位为1,输出1
    
    return 0;
}

结构体指针类型转换

#include <stdio.h>
int main()
{
    struct k1
    {
        char c[4];
        //......
    } ali = {"ali"};
    
    struct k2
    {
        int i;
        //......
    };
    
    struct k1 * p1 = &ali;
    struct k2 * p2 = (struct k2 *)p1;    //p1转换为k2类型
    
    printf("0x%x\n", p2->i);
    
    return 0;
}

空类型指针

指针类型可以定义为void,表示类型为空,空类型指针可以存储任何类型数据的地址。
空类型指针不能直接使用,需要首先转换为具体的类型再使用,否则编译器无法确定要操作多少内存单元。

当你需要接收一个指针但又不确定它的类型时,可以定义一个空类型指针接收,之后再转换为具体类型使用。

#include <stdio.h>
int main()
{
    int a = 9;
    
    int * p1 = &a;
    void * p2 = p1;    //空类型指针
    
    //printf("%d\n", *p2);          //错误,不能使用
    printf("%d\n", *(int *)p2);     //正确,转换为int类型再使用
    
    return 0;
}

 

标签:p2,p1,06,int,C语言,调用,数组,指针
From: https://www.cnblogs.com/alixyy/p/18173710

相关文章

  • 标准C语言5
    进制转换:​ 现在的CPU只能识别高低两种电流,只能对二进制数据进行计算。​ 二进制数据虽然可以直接CPU计算识别,但不方便书写、记录,把二进制数据转换成八进制是为了方便记录在文档中。​随着CPU的不断发展位数不断增加,由早期的8位逐渐发展成现在的64位,因此八进制就不能满......
  • 105. 106. 从中序与后序遍历序列构造二叉树
    https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/思路和106.从中序与后序遍历序列构造二叉树相同/***Definitionforabinarytreenode.*publicclassTreeNode{*intval;*TreeNodeleft;*TreeNoder......
  • 标准C语言4
    一、函数什么是函数:function​函数就是一段具有某一项功能的代码集合,它是C语言中管理代码的最小单位,把具有某项功能的若干行代码封装在函数中方便管理代码且方便重复调用。函数的分类:标准库函数:​ C语言标准委员会为C语言以函数形式提供了一些基础功能,这些函数被封装在li......
  • 信号量 semaphore的实现 -06
    1 semaphore的内核结构体注意:这是信号量,不是信号。在前面学习异步通知时,驱动程序给应用程序发信号。现在我们讲的信号量是一种同步、互斥机制。信号量的定义及操作函数都在Linux内核文件include\linux\semaphore.h中定义,如下: 初始化semaphore之后,就可以使用down函数或其他衍......
  • 标准C语言3
    一、数组什么是数组:​ 数组就是变量的组合,是一种批量定义变量的方式如何定义数组:类型名数组名[数量]; intarr[8];// 相当于定义了8个int类型的变量 inta1,a2,a3,...;访问数组中的变量:数组名[下标]; 下标从0开始,范围0~数量-1遍历数组:与for循环配合,使用循环变量作......
  • 对C语言符号的一些冷门知识运用的剖析和总结
    符号目录符号注释奇怪的注释C风格的注释无法嵌套一些特殊的注释注释的规则建议反斜杠'\'反斜杠有续行的作用,但要注意续行后不能添加空格回车也能起到换行的作用,那续行符的意义在哪?反斜杠的转义功能单引号和双引号字面值,字符串,字符,字符变量的大小为什么sizeof('1')的大小是4?c......
  • 标准C语言2
    二、常量(了解)​ 常量就是程序运行过程中不能改变的量,C语言中常量有:字面值常量、宏常量、枚举常量。字面值常量100 int100l long100ll longlong100u unsignedint100lu unsignedlong100llu unsignedlonglong定义一个宏常量表示100年总共有多少秒,不考虑闰平年 #defin......
  • C语言 子进程段错误后变成僵尸进程
    空指针获取首元素时出现段错误,子进程异常退出,父进程没有处理。#include<stdio.h>#include<unistd.h>intmain(){pid_tpid;pid=fork();if(pid>0){printf("fatherprocessisPID:%d\n",getpid());while(1){......
  • 标准C语言1
    一、C语言介绍​ 丹尼斯.里奇和肯.汤普逊在1971~1973年美国贝尔实验室,在开发UNIX操作系统时,在BCPL语言的基础上(newB语言),发明第一款高级编程语言,取BCPL第二个字母作为名字,所以叫C语言​ BCPL->newB->C->UNIX->Minix->Linux​ 它是为了开发操作系统而研发的一款编程语言,它特......
  • 06.数组
    1.数组概述数组是相同类型数据的有序集合;数组描述的是相同类型的若干各数据,按照一定的先后次序排列组合而成;其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们。2.数组声明创建首先必须声明数组变量,才能在程序中使用数组:dataType[]arrayRefvar;//首......