c语言学习第八天

复习:
程序的内存分段:程序把内存进行分段是为了更合理、方便、高效的使用内存。
text、data、bss 当代码编译完成后,程序的这三个内存的使用计划就已经确定了,在程序运行期间不会发生变量,可以使用 size ./a.out 查看这三个内存段的大小。

    stack、heap 这两个内存段会随着程序的运行而动态变化,在程序运行期间,通过/prco/进程号/maps文件,查看程序的内存使用情况,getpid()函数可以获取程序的进程号。

    text 代码段:编译器会把C代码编译成二进制指令,当程序执行时二进制指令就会被加载到text内存段里,并且该内存段是只读的,如果修改该内存段的数据,会产生段错误。
        r-x 二进制指令
        r-- 常量数据
    data 全局数据段:里面存储着初始化过的全局变量和静态变量。

    bss 静态数据段:里面存储着未初始化过的全局变量和静态变量,在程序执行前会被初始化为0,所以全局变量和静态变量的默认值是0。

    heap 堆内存:由程序员手动管理,使用比较麻烦,特点是足够大,当数据量比较大时,适合存储在该内存段中。

    stack 栈内存:里面存储着局部变量、块变量,由系统自动管理,使用方便,但容量有限,在Linux系统下可以通过ulimit -s 查看栈内存的上限,我们使用ubuntu1604.LTS.32bit默认的栈内存上限是8192KB,如果栈内存超出上限,就会产生段错误或栈崩溃。

变量的属性:
    作用域:变量可以使用的范围。
    存储位置:变量存储在哪个内存段。
    生命周期:变量从定义到释放的时间段。

变量的分类:
    全局变量:定义在函数外
        作用域:程序都可以使用
        存储位置:初始化过的存储在data,未初始化的存储在bss
        生命周期:从程序开始运行 ~ 程序执行结束

    局部变量:定义在函数内
        作用域:它所在的函数内
        存储位置:栈内存
        生命周期:从函数被调用并且执行到变量的定义语句 ~ 函数执行结束释放

    块变量:定义在 if,for,while,do while 语句块内
        作用域:它所在的大括号内
        存储位置:栈内存
        生命周期:从进入语句块并且执行到变量的定义语句 ~ 语句块执行结束

修饰变量的关键字:
    auto:用来修饰自动分配、释放内存的变量(局部变量、块变量),所以不能修饰全局变量和静态变量,在C语言中已经基本不再使用。
        在C++11语法标准下,定义变量时可以不提供变量的类型,它会根据初始化数据的类型自动设置变量的类型。

    typedef:被它修饰过的变量就成为了一种类型,可以为名字比较长的类型取重新取一个简短的名字,如:size_t time_t uint8_t uint16_t uint32_t uint64_t。

    const:能为变量提供保护,被它修饰过的变量不能显式修改(可以通过指针进行修改)。
        原本存储在data的变量,被它修饰过后就会存储到text内存段中,变成真正的常量,强行修改会产生段错误。

    static:
        1、把函数、全局变量作用域限制为只能在它所在的.c文件内使用。
        2、把局部变量、块变量的存储位置由栈改为data或bss。
        3、把局部变量、块变量的生命周期延长到与全局变量相同(但作用域不变)。

    register:申请把变量的存储介质由内存条改为CPU的寄存器,一旦申请成功变量的使用速度将大大提升,为不暴露寄存器的位置,这种变量不能获取地址。

    volatile:告诉系统不要优化变量的读值过程,在多线程共享变量、驱动编程、裸机编程时合适使用该关键字。

    extern:在多文件编程时用来其它.c文件中定义的全局变量。

内存条长什么样的(32位CPU为例):
1、内存条内部就像一叠很厚的条形便签。
2、一张便签可以记录8个二进制,相当于1字节。
3、内存条的大小决定了它内部有多少张便签,每张便签都一个序号(从0开始),我们把每个便签的序称为内存地址。
1G内存 = 1024MB = 1048576kb = 1073741824字节 编号范围是 0~1073741823
4、CPU的位数相当于CPU长了多少根金手指,每根金手指有高低两种电流,高电流代表1,低电流代表0,32位的CPU就相当用32位二进制整数告诉内存条它要访问哪一张便签,它表示的范围是 0x00000000 ~ 0xffffffff,也就是0~4294967295,也就32位CPU最多只能访问 4294967296 张便签。
4294967296字节 = 4194304KB = 4096MB = 4GB

什么是指针:
1、指针是一种数据类型,使用它可以定义指针变量,简称为指针。
2、指针变量中存储的是内存地址(整数,便签的序号),以32位系统为例,指针变量值的范围是:0x00000000~0xffffffff,指针变量占用4字节内存。
3、可以指针变量中存储的整数去访问内存。

如何使用指针:
定义指针变量:类型* 指针变量名;
1、指针变量名中存储的整数,只对应一张便签,如果想访问一个内存块,指针变量中存储的整数就是内存块的第一个字节的地址。
2、指针变量的类型就决定要访问内存时的字节数量。
3、指针变量的取名规则与普通变量一样,但指针变量的使用方式与普通变量完全不同,为了避免与普通变量混用,所以指针变量一般以p结尾。
4、指针变量的默认值与普通变量一样是随机的(野指针),为了安全一般要给指针变量初始化,如果没有合适的内存地址,可以先赋值为NULL(空指针)。
5、指针变量不能连续定义:
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都是指针变量
给指针变量赋值:指针变量 = 内存地址
// 定义的指针变量时初始化
int
p = # // 获取变量的地址赋值给指针变量,num的类型要与指针变量的类型相同,否则编译时会有警告,在解引用时可能出现段错误或脏数据。
// 定义指针变量完成后再赋值
p = malloc(4); // 把堆内存的地址赋值给指针变量
根据指针变量中存储的内存地址访问内存(解引用):指针变量;
int
p = #
*p <=> num; // *p 等价于num
p 访问到的内存字节数由指针变量的类型决定的:
char
p; // p 访问1字节内存
short
p; // p 访问2字节内存
int
p; // p 访问4字节内存
解引用时出现段错误的原因:
1、指针变量中存储的是无效的内存地址(不在maps文件里的地址范围内)。
2、指针变量中存储的是代码段的内存地址。
const int num = 1234; // num存储在代码段中
int main()
{
int
p = # // 指针变量p中存储的是代码段的地址
printf(“%d\n”,*p); // 不会产生段错误
*p = 6666; // 肯定产生段错误
}
3、针变量中存储的是NULL,解引用时肯定产生段错误
注意:解引用产生的段错误,要在指针变量赋值时改正

什么情况下使用指针:
1、使用指针可以在函数之间共享变量。
使用全局变量也可能在函数之间共享变量,但使用全局变量容易造成命名冲突,并且在程序运行期间全局变量所占用的内存是不会被释放的,过多使用容易造成内存浪费,尽量少用全局变量,最好不用。
函数之间共享变量可以获取函数的执行结果,当函数的执行结果是多个数据时,return语句无法解决,必须使用指针变量,例如:time就可以以指针的方式获取系统时间。

2、使用指针可以提高函数之间的传参效率。
    C语言的函数传参都是值传递(赋值,内存拷贝),比如:double,long double,long long,自定义类型(结构、联合、枚举、类),它们都是字节数>=8的变量,如果是值传递则最小需要拷贝8字节内存,如果传递变量的地址,只需要拷贝4字节内存。
    // 以下代码传递变量的地址会比传递变量的值节约了2.5秒
    void func(const long double* p)
    {

    }
    int main()
    {
        long double f = 3.14;
        for(int i=0; i<1000000000; i++)
        {
            func(&f)
            f+=i;
        }
    }
    注意:传递变量的地址虽然提高了传参效率,但会让变量有被修改的风险,解决方法就是用const修改指针变量。

3、使用堆内存时必须与指针变量配合。
    当执行 int num; 语句时,系统会分配4字节内存(text、data、bss、stack),并让这4字节内存与num变量名建立联系,在之后的代码中使用num就相当于使用这4字节内存,这就是为内存取名字。
    而堆内存无法做到这样,当向系统申请一块堆内存时,系统只会返回值这块内存的首地址,这块内存无法与一个变量名建立联系,也就是无法取名字,因此必须与指针配合才能使用。

作业1:实现一个交换两个变量的函数。
void swap(int* p1,int* p2);

作业2:实现一个函数用于计算两整数的最大公约数和最小公倍数,最大公约数用return返回,最小公倍数用指针处理。
int max_min(int num1,int num2,int* p)
{
*p = 最小公倍数;
return 最大公约数;
}

指针的运算、二级指针、万能指针、数组指针、指针数组、函数指针、指针函数,数组名与指针、const与指针、堆内存、字符串、结构指针、结构成员指针…

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值