c语言学习第九天

复习:
什么是指针:
1、指针是一种数据类型,使用它可以定义指针变量,我们也把指针变量简称为指针。
2、指针变量中存储的是代表内存地址的整数(32位系统的指针变量是4字节,64系统的指针变量是8字节),整数范围:0x00000000~0xffffffff。
3、可以根据指针变量中存储的内存地址去访问对应的内存。

为什么使用指针:
    1、使用指针可以跨函数共享蛮,这样可以获取函数的执行结果(return语句只能返回一个数据),比如:scanf("%d",&num);
    2、使用指针可以提高函数的传参效率。
    3、堆内存无法取名字,使用堆内存必须与指针配合。

如何使用指针:
    定义指针变量:类型* 指针变量名;
        1、指针变量与普通变量的取名规则一样,但使用方式完全不同,为了避免滥用,一般让指针变量以p结尾。
        2、指针变量与普通变量一样默认值是随机的(野指针),为了安全要对指针变量初始化,如果不知道该赋值什么值,可以先赋值为NULL(空指针)。
        3、指针变量不能连续定义:
            int* p1,p2,p3; // p1是指针变量,p2,p3只是普通的int类型变量
            int *p1,*p2,*p3; // p1,p2,p3都是指针变量
            typedef int* intp;
            intp p1,p2,p3; // p1,p2,p3都是指针变量
        4、指针变量中记录的某个字节的内存地址,它代表着一个内存块的首地址。
        5、通过指针变量访问内存时,具体访问多少个字节由指针变量的类型决定。

    给指针变量赋值:指针变量 = 内存地址;
        int* p = # // 获取普通变量的地址给指针变量初始化,指针变量的类型要与普通变量的类型相同,否则编译时会出现警告,并且在解引用时产生段错误或脏数据
        p = malloc(4); // 从系统获取一块堆内存,把内存块的首地址赋值给指针变量

    解引用:根据变量中存储的内存地址访问对应的内存,*指针变量。
        int* p = #
        *p <=> num;
        对指针变量解引用时可能出现段错误、脏数据等情况:
            原因1:赋值的内存地址是非法的,不在maps文件地址表范围内。
            原因2:修改了text内存段的内容。
            const int num = 1234;
            int main()
            {
                int* p = &num;
                printf("%d\n",*p); // 可以读
                *p = 6666; // 肯定会出现段错误
            }
        解引用时访问的内存字节数由指针变量的类型决定:
            char* p; *p // 访问1字节
            short* p; *p // 访问2字节
            int* p; *p // 访问4字节
            double* p; *p // 访问8字节
        *在C语言中有三个作用:
            1、乘法运算
            2、定义指针变量
            3、对指针变量解引用

指针变量的输出:
1、以无符号整数方式显示,但编译器会有警告。
2、%p 以十六进制显示

使用指针要注意的问题:
空指针:值为NULL的指针变量叫空指针,系统规定不能对空指针解引用,否则会产生段错误。
如何避免空指针产生的段错误:对来历不明的指针解引用前,先判断是否是空指针。
1、当实现的函数的参数是指针,所以我们无法确定调用者传递过来的指针是否是空指针,因此在解引用前要先判断。
2、当函数的函数返回值是指针类型,如果函数执行出错则会返回空指针(空指针是函数执行出错一标志),对返回值进行判断,既可以知道函数执行是否出错,也可以避免对空指针解引用,比如:malloc执行出错误返回值是就空。
注意:大多系统的NULL是0地址,但是有少数系统的NULL是1地址,所以判断空指针时
if(!p) // 不通用,不建议这样写
{

    }
    if(p == NULL) // 这种写法有瑕疵,当=少写一个是编译器不会报错,就变成了赋值,虽p肯定变量成空指针,接下来解引用时肯定出现段错误
    {

    }
    if(NULL == p) // 完美
    {

    }

野指针:不知道指针变量中存储的地址是否合法,这种指针叫野指针。
    对野指针解引用时可能产生的后果:
        1、段错误
        2、脏数据
        3、一切正常
    野指针与空指针相比危害更大,因为无法判断一个指针是否是野指针,所以野指针的错误具有隐藏性、潜伏性、随机性,一旦出错很难寻找、定位、重现。
    如何避免野指针产生的危害:
        前提:所有的野指针都是人为制造出来的,所以只要不制造野指针,就不使用野指针。
        1、定义指针变量一定要初始化,宁可赋值为NULL,不要产生野指针。
        2、函数不要返回局部变量的地址,因为随着函数的执行结束,属于变量的内存会随即释放,因此这种情况返回的就是野指针。
        3、当堆内存被释放后,与它配合的指针变量要及时赋值为NULL。

const与指针:
const与指针配合一共有5种写,3种功能:
功能1:保存指针变量所指向的内存不被p修改
const int
p;
int const * p;
p = NULL; // OK
*p = 6666; // NO
当使用指针优化函数的传参效率时,变量就有被修改的风险,这种情况可以用const与指针配合,防止变量在被调用函数里修改。

功能2:保存指针变量不被修改,防止指针变量改变指向。
    int * const p;
    p = NULL; // NO
    *p = 6666; // OK
    当使用数组作函数的参数时,数组就脱变成了指针,要想让指针一直指向数组,可以使用const加以保护。

功能3:既保存指针变量也保存它指向的内存。
    const int * const p;
    int const * const p;
    p = NULL; // NO
    *p = 6666; // NO

指针的运算:
前提:指针变量中存储的是代表内存地址的整数,理论上整数可以使用的运算符,指针变量都可以使用,但只有指针加/减整数或者指针-指针才有意义。
指针的进步值:指针变量解引用时所访问内存字节数,也就是指针变量加1时前进的字节数。

指针+n 结果是代表地址的整数+进步值*n
    int* p = 0;
    p+3 得到的结果12
指针-n 结果是代表地址的整数-进步值*n
    short* p = 10;
    p-4 得到的结果是2
指针-指针 结果是(代表地址的整数-代表地址的整数)/进步值
    int* p1 = 18;
    int* p2 = 10;
    p1-p2 得到的结果是2
注意:指针变量加/减一个整数时,相当于指针变量前后移动(以进步值为单位移动),指针-指针可以计算出两个地址之间隔子多少个元素(指针变量解引用的对象)。

指针与数组名:
数组名:数组名就是个地址,所以数组作为函数的参数时才能脱变成指针,它就是数组内存块的首地址,也是第一个元素的首地址,所以它可以使用解引用访问数组中的每个元素。
int arr[5] = {1,2,3,4,5}; // arr 的类型就是int*
for(int i=0; i<5; i++)
{
printf("%d ",*(arr+i));
}
*(arr+i) <=> arr[i] // 这两种用法是等价的

数组名可以使用指针的语法,指针也可以使用数组的语法:
int arr[5] = {1,2,3,4,5};
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类型的指针作函数的参数。

练习:实现一个清理数组的函数,把数组中的每个元素都赋值为零。
void clean_arr(void* addr,size_t size)
// size是数组的字节数,只要把数组中的每个字节赋值为0,那么每个元素就赋值为0了。

作业:实现一个内存拷贝函数,把一块内存中的数据拷贝到另一块内存,考虑dest与src有交集情况。
void mem_copy(void* dest,void* src,size_t size);
情况1:
int arr[6] = {1,2,3,4};
mem_copy(arr+2,arr,16);
情况2:
int arr[8] = {1,2,3,4,5,6,7,8};
mem_copy(arr,arr+3,20);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值