《C语言学习笔记10》指针入门

内存地址:
单位 为 字节 : 字节是一个内存的 容量单位 ,一个字节有8个位( 1byte = 8bit )
地址: 系统为了方便管理内存,给每一个字节容量都编了一个 号码 ,这个号码就是 内存的地址
一个字节一个地址(最小单位)
《C语言学习笔记10》指针入门_第1张图片

基地址 (首地址):
如下图:
一个 整型的数据需4个字节 的容量来存储,因此一共拥有4个内存地址。地址值最小的作为该数
据的 入口地址 ,就称为该数据 基地址或首地址 注意:数组入口地址为低地址
int a=12345678,   1端为高位,8端为低位

 《C语言学习笔记10》指针入门_第2张图片

 通过地址可以访问内存空间,既可以读取数据《C语言学习笔记10》指针入门_第3张图片

结论:
对于单字节的数据而言他的地址就成他的基地址。
对于 多字节 的数据而言他的 地址中地址值最小 的作为他的基地址。
取地址符:
& 作用是 取得某一个变量的基地址
如何输出一个数据的基地址:
1 使用格式控制符 %p 来输出,打印指针
2 int a = 0x12345678;
3
4 printf("a的地址为:%p\n" , &a ); // 0x7ffedda99114
5 printf("a的地址为:%ld\n" , &a ); // 140732617298196

注意: 以上运行结果告诉我们,变量定义的顺序并不会影响数据所在的内存地址(次序)。
编译器会根据数据的尺寸来选择合适的地址来分配(地址对齐的问题,每块地址都有合适的尺寸)。
练习:
尝试打印数组的内存地址 (注意:只有数组的数组名才能直接当成首地址·,其他类型的不能)
                                           int a=100;这个变量a就不能作为首地址使用
《C语言学习笔记10》指针入门_第4张图片

#include 

int main()
{
    int a1[5][5];
    int b1[5];
    int a=0x12345678;
    char b;
    long c;
    short d;
    long long e;

    printf("the address of a is:%p\n",&a);
    printf("the address of b is:%p\n",&b);
    printf("the address of c is:%p\n",&c);
    printf("the address of d is:%p\n",&d);
    printf("the address of e is:%p\n",&e);

    printf("a1[0]   =0x%p\n",&a1[0]);
    printf("a1[0][0]=0x%p\n",&a1[0][0]);
    printf("a1      =0x%p\n",&a1);
    printf("a1[0][0]=0x%p\n",&a1[0][1]);
    printf("a1[0][0]=0x%p\n",&a1[1][0]);
    return 0;
}
下图中可以看出 &arr 与&arr[0] 的输出结果一致。
原因 arr 表示该数组的(首元素)入口地址,arr[0] 该数组的第一个元素的入口地址。

《C语言学习笔记10》指针入门_第5张图片

指针的基础:
1、什么是指针

        在程序中,一般是通过变量名来对内存单元进行存取操作的。其实程序经过编译后已经将变量名转变为变量的地址。对变量值的存取都是通过地址进行的。这种按照变量地址存取变量值的方式称为:直接存取方式,或直接访问。

2、定义指针变量(指针变量只能用来存储地址,不能存放其他变量)

定义指针的一般形式:基类型 *指针变量名

如: int i,j;

int  *pointer_1, *pointer_2;  //定义指针变量pointer_1,pointer_2

注意:指针变量名是pointer_1,pointer-2,而不是*pointer_1,*pointer_2,既“*”不是指针变量名的一部分。在变量名前加一个“*”表示该变量是指针变量。既定义为 int *    .

概念: 指针也是一个 变量 ,只不过该变量用来存放某一个 数据的地址 (基地址)。
如何定义:
 指向数据的类型 * 指针的名称 ;
 int * ptr1 ;
 char * ptr2 ;
如何初始化化:
 int a = 250 ;
 int * ptr = &a ;
 // 1 向系统申请一个内存 名字设置为 ptr
 // 2 * 该内存用来存放一个数据的内存地址
 // 3 int 该内存地址中应该用来存放一个整型数据
 // 该指针用来指向的数据应该是一个整型的数据
 // 4 把变量a的内存地址取出来并存入到 该内存中
示例代码:
/*
 * @Author: error: git config user.name && git config user.email & please set dead value or install git
 * @Date: 2022-07-21 14:12:46
 * @LastEditors: error: git config user.name && git config user.email & please set dead value or install git
 * @LastEditTime: 2022-07-21 19:35:38
 * @FilePath: \share code\day08\ex01.c
 * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 */

// #if 0
// #include 

// int main()
// {
//     int a1[5][5];
//     int b1[5];
//     int a=0x12345678;
//     char b;
//     long c;
//     short d;
//     long long e;

//     printf("the address of a is:%p\n",&a);
//     printf("the address of b is:%p\n",&b);
//     printf("the address of c is:%p\n",&c);
//     printf("the address of d is:%p\n",&d);
//     printf("the address of e is:%p\n",&e);

//     printf("a1[0]   =0x%p\n",&a1[0]);
//     printf("a1[0][0]=0x%p\n",&a1[0][0]);
//     printf("a1      =0x%p\n",&a1);
//     printf("a1[0][0]=0x%p\n",&a1[0][1]);
//     printf("a1[0][0]=0x%p\n",&a1[1][0]);
//     return 0;
// }

// #endif


#include 

int main()
{
    int a=250;
    int *ptr=&a;
    int arr[10]={1,2,3,4,5,6,7,8,9,10};
    int *p=arr;
    int *p1=arr;


    printf("p1=       %p\n",p1);
    p1=arr+1; //p1指向arr数组的基地址+1(+1简单地址+1)
    printf("p1=arr+1= %p1\n",p1); 
    p1= &arr+1; //p1指向arr数组的基地址+sizeof(arr)*1的位置处
    printf("p1=&arr+1=%p1\n",p1);  
 

    printf("a:addres=%p\n",&a);
    printf("ptr=%p\n",ptr);
    printf("&ptr=%p\n",&ptr);
    printf("*ptr=%d\n",*ptr); 

    printf("*p=%d\n",*p);
    printf("p[1]=%d\n",p[1]);//1、等价于 p[1]=*(p+1),内部规定解引用
                             //2、记住 *(p+i) ==  p[i]
                            //3、或可以理解你把一维数组的首元素地址a(就是&a[0] = a,
                            //数组名a就是数组的首元素地址)给了p,p其实就等价于a,那p[2]==a[2]了

    printf("*(p+2)=%d\n",*(p+2));//(指针变量偏移两个int类型)在指向存储内容
}
     
 

《C语言学习笔记10》指针入门_第6张图片

《C语言学习笔记10》指针入门_第7张图片

如何引用(如何通过指针来访问数据):
int a = 250 ;
int * ptr = &a ;
printf("*ptr:%d\n" , *ptr );

*ptr = 550 ;
注意:
ptr --> &a
* ptr ---> a
& 与 * 可以相互抵消
指针的大小:
指针的大小与指针是指向的数据类型 没有 任何关系,它与 地址的大小 有关系,地址大小由系统的位
决定 。如果系统是64位那么指针就一定是 8个字节。
#include 

int main()
{
    int *p1;
    char *p2;
    long long  *p3;
    printf("sizeof(p1)=%ld\n",sizeof(p1));
    printf("sizeof(p2)=%ld\n",sizeof(p2));
    printf("sizeof(p3)=%ld\n",sizeof(p3));
}

总结:
指针本身就是一个 变量 , 该变量用来 存储一个数据内存地址
指针如何定义 int * p ;
可以通过 * 解引用 、 取目标
指针的大小由系统的位数决定
主机字节序:
由与世界上有不同的计算机制造商因此生产出来的计算机硬件会有一些小的差异,其中一个差异就
是所谓的字节序,字节序就是一个数据的存储形式 。
字节序分为两种:
大端序:把高有效位存放在低地址中
小端序:把 低有效位 存放在 低地址
整个系统的字节序在出厂的就已经决定(硬件+系统),整个系统中所有的数据都会按照特定也存
储的序列进行存储。
指针运算:
指针是允许进行加减运算的,它加减的大小 与该指针本身的类型有关系
注意:指针本身的大小与它的数据类型无关,由系统而定,32位系统为4个字节,64位系统为8个字节。但是指针的运算时与他的数据类型有关的,规则如下:
指针加法: 当指针p + n 的时候 ,则是以当前p的指向为基准增加 n * 指针类型大小
指针减法: 当指针p - n 的时候 ,则是以当前p的指向为基准减少 n * 指针类型大小
《C语言学习笔记10》指针入门_第8张图片

《C语言学习笔记10》指针入门_第9张图片
初始元素间隔有赋值数组类型而决定
《C语言学习笔记10》指针入门_第10张图片

结果:1  5  2

 《C语言学习笔记10》指针入门_第11张图片

#include 

int main(int argc, char **argv)
{
    char a[10]={1,2,3,4,5,6,7,8,9,10};
    
    int *pi=a;
    char *pc=a;

    printf("pi=%#p\n",pi);
    printf("pc=%#p\n",pc);

    pi++;
    pc++;

    printf("pi=%#p\n",pi);
    printf("pc=%#p\n",pc);


    printf("*pi=0x%08X\n",*pi);//int类型,解引用后出现四个字节,占据数组四个位,输出四个值
    printf("*pi=0x%02X\n",*(char *)pi);//强制类型转换,输出一个数
    printf("*pi=0x%X\n",*(short *)pi);
    printf("*pc=%d\n",*pc); 


    return 0;
}

 《C语言学习笔记10》指针入门_第12张图片

指针指向一个数组:
#include 
int main()
{
    int arr[10] = {1,250,3,4,5,6,7,8,9,0};

    int * p = arr ; // 定义一个指针p 并让他执行了数组arr的首元素的首地址

    printf("*p:%d\n" , *p );
    printf("*(p+1):%d\n" , *(p+1) );
    printf("*(p+5):%d\n" , *(p+5) );
}
试题:
 int arr[5] = {1,2,3,4,5}; 
 int *p = arr;
 int *p1 = &arr+1;
 printf("%d , %d , %d \n" , *p , *(p1-1) , *(p+1));
答案:
输出 : 1 5 2
野指针:
概念: 一个指向了 未知区域 的指针变量或是一个 指向非法内存 的指针变量。
 int a ; // 定义变量a但是没有赋值因此它的值是随机值
 int * p ; // 定义变量p但是没有赋值因此它的值是随机值 他就是一个野指针
 int * p1 = malloc(1024); // 定义变量p1 并使用mallo 分配了1024字节的内存给他
 free(p1) ; // 通过free函数来释放p1所指向的内存
 // 当前p1依旧指向刚才申请后并释放了的内存
 // 换而言之 p1指向了一个已经被系统回收的内存属于非法内存

int *p=NULL;//定义指针变量,暂时不使用,可以给它NULL;(就是一个空指针,它指向0地址)
《C语言学习笔记10》指针入门_第13张图片

危害:
对于野指针的访问有可能会导致 程序崩溃 严重的更会导致 系统崩溃
如何产生野指针:
        在定义的时候没有进行初始化
        该指针所执行的内存被回收释放了
        指针运算也有可能导致指针越界(有可能指向非法内存)
如何避免:
        定义的时候初始化
        不要使用被释放内存后的指针,如果要用则需要从新赋值
         明确指针的运算问题不要越界
        
空指针:
概念: 一个指向空的指针。
在某些指针被定义出来的时候不能立马确定他的指向,这个时候就会出现野指针,为了避免野指针
导致系统崩溃,则可以在定义是初始化该指针为NULL 。
NULL 实际上就是 0 ;可以理解为指针指向了地址值为 0 的一个地址
《C语言学习笔记10》指针入门_第14张图片

实例:
1 char * p = NULL ;
2
3 printf ( "&p:%p\n" , & p ); // 直接输出变量 p 自己的地址 4
5 printf ( "p:%p\n" , p ); // 输出变量p 中所存放的数据(null)
6
7 [ 错误示范 ]
8 * p = 250 ; // 250 数据存入到 p 所指向的内存空间, 由于 p 指向的是 Null 因此会导致段错误
9 printf ( "*p:%p\n" , * p ); // 尝试获得 p 所指向内存中的值, 由于 p 指向的是 Null 因此会导致段错误
空指针的作用:
阻止野指针的产生,因为野指针他的指向是完全不可预测的,也就是有可能会指向系统的关键性数
据、代码,如果不小心操作了可能会导致系统崩溃, 但是空指针最多会导致程序的崩溃并 不会上升到系统级别的问题 。程序运行的时候也可以通过对指针的判断来决定某些代码的运行方向(作为函数的 参数判断来使用)。
数组与指针:
数组的名字在很多情况下 表示 一个指针,该指针指向该数组的 第一个元素的入口地址 (首地址)。
《C语言学习笔记10》指针入门_第15张图片
注意注意!!!!
        数组指针其实是一个二级指针,它的每一个存储内容为 int*,既每一个存储内容为 *p,所以给他赋值就相当于 *p=arr。因为,数组指针存的是地址,故*p存得就是地址,所以你要输出数值,要解引用,既**p
《C语言学习笔记10》指针入门_第16张图片

#include 

int main(int argc, char const *argv[])
{

    int arr[10] = {1, 3, 5, 7, 9, 2, 4, 6, 8, 0};

    int *p1 = arr; // 定义一个整型指针,该指针指向数组arr的首元素的首地址 1的地址

    // 一下三个语句的输出一致
    printf("arr:%p\n", arr);
    printf("&arr:%p\n", &arr);
    printf("p1:%p\n", p1);

    // 如何通过指针 p1 来访问整个数组
    for (int i = 0; i < 10; i++)
    {
        printf("%p:%d\n", (p1 + i), *(p1 + i)); // (p1+i) 是对指针p1的运算,由于p1 是整形的指
                                                // (p1+i) 运算后依旧是一个地址值, 因此 * 则用来访问该地址中的数据
    }

    //如何定义一个用来指向数组的指针 --> 数组 指针
    // int * p ;
    // int [10]
    int(*p2)[10] = &arr; // 定义一个数组指针 p2 , &arr 表示整个数组的地址,数组指针规定写法int (*p2)[]

    printf("p2+1:%p\n", p2 + 1); // p2 是一个数组类型的指针,因此+1 则加一个数组的大小

    // 如何通过 p2来访问数组arr的内容
    for (int i = 0; i < 10; i++)
    {
        // *p2 对指针p2进行一次解引用 得到的是数组中的第一个元素的地址
        // ** p2 对指针p2进行两次解引用。 得到的是数组中第一个元素的地址中的值
        printf("%p:%d\n", *p2 + i, *(*p2 + i));
    }

    return 0;
}

《C语言学习笔记10》指针入门_第17张图片

总结:
使用指针来指向数组有两种方式:
普通数据类型的指针:
当该指针需要进行加减运算的时候他的加减单位是普通的数据类型的大小
int *p;
p+=1;//加一个int类型(4个字节)

数组类型的指针:
当该指针需要加减运算的时候它的加减单位则是一整个数组的大小
int a[5]={1,2,3,4,5};

int *p=&a+1;//指向数组的基地址+sizeof(a)*1的位置

你可能感兴趣的:(c语言,学习,开发语言,vscode)