“21好习惯“第一期-14

定义指针变量

C语言中,定义变量时,在变量名 前 写一个 * 星号,这个变量就变成了对应变量类型的指针变量。必要时要加( ) 来避免优先级的问题。

引申:C语言中,定义变量时,在定义的最前面写上typedef ,那么这个变量名就成了一种类型,即这个类型的同义词。

int a ; //int类型变量 a

int *a ; //int* 变量a

int arr[3]; //arr是包含3个int元素的数组

int (* arr )[3]; //arr是一个指向包含3个int元素的数组的指针变量

 

 

//-----------------各种类型的指针------------------------------

 

int* p_int; //指向int类型变量的指针

 

double* p_double; //指向idouble类型变量的指针

 

struct Student *p_struct; //结构体类型的指针

 

int(*p_func)(int,int); //指向返回类型为int,有2个int形参的函数的指针

 

int(*p_arr)[3]; //指向含有3个int元素的数组的指针

 

int** p_pointer; //指向 一个整形变量指针的指针

取地址

既然有了指针变量,那就得让他保存其它变量的地址,使用& 运算符取得一个变量的地址。

int add(int a , int b)

{

    return a + b;

}

 

int main(void)

{

    int num = 97;

    float score = 10.00F;

    int arr[3] = { 1,2,3};

 

    //-----------------------

 

    int* p_num = #

    float* p_score = &score;

    int (*p_arr)[3] = &arr;          

    int (*fp_add)(int ,int )  = add;  //p_add是指向函数add的函数指针

    return 0;

}

特殊的情况,他们并不一定需要使用&取地址:

数组名的值就是这个数组的第一个元素的地址。

函数名的值就是这个函数的地址。

字符串字面值常量作为右值时,就是这个字符串对应的字符数组的名称,也就是这个字符串在内存中的地址。

int add(int a , int b){

    return a + b;

}

int main(void)

{

    int arr[3] = { 1,2,3};

    //-----------------------

    int* p_first = arr;

    int (*fp_add)(int ,int )  =  add;

    const char* msg = "Hello world";

    return 0;

}

解地址

我们需要一个数据的指针变量干什么?当然使用通过它来操作(读/写)它指向的数据啦。对一个指针解地址,就可以取到这个内存数据,解地址 的写法,就是在指针的前面加一个*号。

解指针的实质是:从指针指向的内存块中取出这个内存数据。

int main(void)

{

    int age = 19;

    int*p_age = &age;

    *p_age  = 20;  //通过指针修改指向的内存数据

 

    printf("age = %d\n",*p_age);   //通过指针读取指向的内存数据

    printf("age = %d\n",age);

 

    return 0;

}

指针之间的赋值

指针赋值和int变量赋值一样,就是将地址的值拷贝给另外一个。指针之间的赋值是一种浅拷贝,是在多个编程单元之间共享内存数据的高效的方法。

int* p1  = & num;

int* p3 = p1;

 

//通过指针 p1 、 p3 都可以对内存数据 num 进行读写,如果2个函数分别使用了p1 和p3,那么这2个函数就共享了数据num。



空指针

指向空,或者说不指向任何东西。在C语言中,我们让指针变量赋值为NULL表示一个空指针,而C语言中,NULL实质是 ((void*)0) , 在C++中,NULL实质是0。

换种说法:任何程序数据都不会存储在地址为0的内存块中,它是被操作系统预留的内存块。

下面代码摘自 stdlib.h

#ifdef __cplusplus

     #define NULL    0

#else   

     #define NULL    ((void *)0)

#endif

5

坏指针

指针变量的值是NULL,或者未知的地址值,或者是当前应用程序不可访问的地址值,这样的指针就是坏指针,不能对他们做解指针操作,否则程序会出现运行时错误,导致程序意外终止。

任何一个指针变量在做 解地址操作前,都必须保证它指向的是有效的,可用的内存块,否则就会出错。坏指针是造成C语言Bug的最频繁的原因之一。

下面的代码就是错误的示例。

void opp()

{

     int*p = NULL;

     *p = 10;      //Oops! 不能对NULL解地址

}

 

void foo()

{

     int*p;

     *p = 10;      //Oops! 不能对一个未知的地址解地址

}

 

void bar()

{

     int*p = (int*)1000;

     *p =10;      //Oops!   不能对一个可能不属于本程序的内存的地址的指针解地址

}

指针的2个重要属性

指针也是一种数据,指针变量也是一种变量,因此指针 这种数据也符合前面 变量和内存 主题中的特性。 这里我只想强调2个属性: 指针的类型,指针的值。

int main(void)

{

    int num = 97;

    int *p1  = #

    char* p2 = (char*)(&num);

 

    printf("%d\n",*p1);    //输出  97

    putchar(*p2);          //输出  a

    return 0;

}

指针的值:很好理解,如上面的num 变量 ,其地址的值就是0028FF40 ,因此 p1的值就是0028FF40。数据的地址用于在内存中定位和标识这个数据,因为任何2个内存不重叠的不同数据的地址都是不同的。

指针的类型:指针的类型决定了这个指针指向的内存的字节数并如何解释这些字节信息。一般指针变量的类型要和它指向的数据的类型匹配。

由于num的地址是0028FF40,因此p1 和 p2的值都是0028FF40

*p1 : 将从地址0028FF40 开始解析,因为p1是int类型指针,int占4字节,因此向后连续取4个字节,并将这4个字节的二进制数据解析为一个整数 97。

*p2 : 将从地址0028FF40 开始解析,因为p2是char类型指针,char占1字节,因此向后连续取1个字节,并将这1个字节的二进制数据解析为一个字符,即’a’。

同样的地址,因为指针的类型不同,对它指向的内存的解释就不同,得到的就是不同的数据。

void*类型指针

由于void是空类型,因此void*类型的指针只保存了指针的值,而丢失了类型信息,我们不知道他指向的数据是什么类型的,只指定这个数据在内存中的起始地址,如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。因为,编译器不允许直接对void*类型的指针做解指针操作。

结构体和指针

结构体指针有特殊的语法: -> 符号

如果p是一个结构体指针,则可以使用 p ->【成员】 的方法访问结构体的成员

typedef struct

{

    char name[31];

    int age;

    float score;

}Student;

 

int main(void)

{

    Student stu = { "Bob" , 19, 98.0};

    Student*ps = &stu;

 

    ps->age = 20;

    ps->score = 99.0;

    printf("name:%s age:%d\n",ps->name,ps->age);

    return 0;

}

数组和指针

1、数组名作为右值的时候,就是第一个元素的地址。

int main(void)

{

    int arr[3] = { 1,2,3};

 

    int*p_first = arr;

    printf("%d\n",*p_first);  //1

    return 0;

}

2、指向数组元素的指针 支持 递增 递减 运算。(实质上所有指针都支持递增递减 运算 ,但只有在数组中使用才是有意义的)

int main(void)

{

    int arr[3] = { 1,2,3};

 

    int*p = arr;

    for(;p!=arr+3;p++){

        printf("%d\n",*p);

    }

    return 0;

}

3、p= p+1 意思是,让p指向原来指向的内存块的下一个相邻的相同类型的内存块。

​ 同一个数组中,元素的指针之间可以做减法运算,此时,指针之差等于下标之差。

4、p[n] == *(p+n)

​ p[n][m] == ( (p+n)+ m )

5、当对数组名使用sizeof时,返回的是整个数组占用的内存字节数。当把数组名赋值给一个指针后,再对指针使用sizeof运算符,返回的是指针的大小。

这就是为什么我么将一个数组传递给一个函数时,需要另外用一个参数传递数组元素个数的原因了。

int main(void)

{

    int arr[3] = { 1,2,3};

 

    int*p = arr;

    printf("sizeof(arr)=%d\n",sizeof(arr));  //sizeof(arr)=12

    printf("sizeof(p)=%d\n",siz
作业:#include<stdio.h>
int main(){
 int a,b,c,d,e,f,n;
 float m;
 printf("Please input n:\n");
 scanf("%d",&a);
 b=a/100;
 c=a%100;
 d=b+c;
 e=b-c;
 f=b*c;
 printf("%d,%d\n",b,c);
 printf("sum=%d,sub=%d,multi=%d\n",d,e,f);
 if(c!=0){
        m=(float)b/c,n=b%c;
    printf("dev=%.2f,mod=%d\n",m,n);
 }
 else{
    printf("The second operator is zero!\n");
 }
 return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值