C语言学习记录230616

        感觉都要成惯例了,当天写前一天的博文,但其实并不是这样,真的是有各种各样的原因。昨天写前一天的博文是因为前一天晚上出去逛街买衣服去了,昨天本来想在下班后写,奈何下班以后到家再上完课,已经十一点半,敲完作业代码就已经凌晨了,索性还是到第二天早上再写,在有所遗忘的情况下总结学习,或许能记住更多东西。

        这天有一些趣闻,但我感觉不是很有趣,放到博文里,没法让人会心一笑就没啥意思了,这次还是直接切入正题吧。

        (一)static修饰内部变量、外部变量、函数

                static这个英文的意思是“静止的、静态的、不变的”,可以用static修饰变量和函数,但是它修饰内部变量、外部变量以及函数时,会产生不同的效果。我们先不直接讨论static,我们先讨论一下这些变量在内存中是存放在哪些区域的,或者说内存中是如何存放他们的。

                内存里可以简单的分为三个区块,分别为栈区、堆区和静态区。内部变量、形式参数等临时变量,它们是存放在栈区的,这个区域里的数据特点就是存储期不长,普遍是自动存储期;如malloc、realloc和calloc等动态分配的内存空间则放置在堆区,这里的数据需要自己主动释放;静态区顾名思义,这里的数据存储期都较长,大多为静态存储期,主要存放的是外部变量和静态变量。此外函数和外部变量还具有一个属性,叫外部链接属性,这个属性的作用就是外部的文件可以引用它,函数和外部变量丧失这个属性后,它就不能再被外部的文件所使用。了解了这个后,我们接下来拆分讨论下static对内部变量、外部变量以及函数的影响。

                ①static修饰内部变量(局部变量)

                正常情况下内部变量存放在栈区,它具有自动存储期以及块作用域,而当用static修饰了内部变量以后,这个声明的时候,它存放在内存的位置就不再是栈区,而是静态区,不难想象,它的存储期可能会从自动存储期变为静态存储期,事实上也是如此。但注意,其作用域并未发生改变,仍然是从声明开始至函数结束。

                这里有一点是之前一直困扰我的,万幸上了课以后,刚好有其他人问了老师这个问题,我也顺便就知道了答案,接下来会写一小段代码,我们讨论下这个static修饰的局部变量声明问题。

#include<stdio.h>

void Output_number(void)
{
    static int num=1;
    num++;
    if(num!=2)printf(" ");
    printf("%d",num);
}

int main(void)
{
    int i;
    for(i=0;i<10;i++)
    {
        Output_number();
    }
    
    return 0;
}

                这段代码的主函数设置了一个循环10次的循环,每次进入循环体都会执行Output_number这个自定义函数,而这个自定义函数的作用就是输出自定义函数内定义的内部变量num的值。此前我虽然知道这个程序最后输出的结果会是2 3 4 5 6 7 8 9 10 11,但我也一直有疑惑,因为主程序每次循环体重新进入自定义函数Output_number时,开头都有static int num=1这段声明和初始化num的语句,那么为什么num没有因为被重新声明报错或者因此而反复重置,导致输出的结果是2 2 2 2 2 2 2 2 2 2?如果一定要打破砂锅问到底,那我大概还是不清楚,但是现在能清楚明白的一点就是,当第一次进入Output_number这个自定义函数时,开头声明定义了静态内部变量i,这句话结束以后,后续程序再次进入这个自定义函数的时候,就不会再执行这条语句(在汇编语言内可以看到这句话被跳过,根本没有被编译过来)。从结果角度看,因为被跳过了,所以后续变量i既不会因为被重复声明而报错,也不会因为被初始化而变为1。所以最后输出结果会是预期的2 3 4 5 6 7 8 9 10 11。

                ②static修饰外部变量(全局变量)

                外部变量具有外部链接属性,这个属性意味着当其他源文件内没有声明这个外部变量,他也能够使用这个变量,使用这个外部变量的方法就是在没有对应外部变量的c声明的c文件里头,在开头加上声明。举例:a.c文件里有一个全局变量i,那么b.c文件要使用这个变量时,需要先写一个声明,extern int i;,extern的英文含义是外部,这边就类似于函数声明、函数原型,是用来告诉编译器这个i是一个外部变量,自己不是没有声明它。因为具有外部链接属性,所以b.c文件可以使用这个在a.c里声明的全局变量i。

                那加了static以后,这个外部变量会怎样呢?看了一本书,那本书里对这块描述我觉得还是很有意思的,那本书里说.c文件是模块式的,当外部变量用static修饰后,这个外部变量将会被对应的模块私有化,其他模块将无法使用它。说人话,加了static以后,这个外部变量i将只有a.c能使用,b.c将无法使用这个全局变量。

                这个结果的本质原因是,static将外部变量所具有的外部链接属性改为了内部链接属性,丧失了外部链接属性后,外部变量就被私有化,不再能被其他模块使用。

                ③static修饰函数

                static修饰函数跟外部变量基本一致,结果也是被对应模块私有化,原因也是外部链接属性被改为内部链接属性。那么static修饰函数跟修饰外部变量有什么区别呢?以后再讨论

        (二)指针

                说指针之前,可以简单了解下内存,为了有效使用内存,我们将内存划分为了一个个内存单位,每个内存单元的单位都是1byte,为了使用方便,我们还给每个内存单元编了号,这个编号就是地址,也就是我们所说的指针,指针跟地址是一回事。那这个地址有多长呢?这个取决CPU生成的虚拟地址是多少位的,32位的虚拟地址说明这个地址有32位,也就是4个字节,64位的虚拟地址,则是8个字节,好了,知道这些以后,我们开始讨论指针。

                前面也说到了指针就是地址,地址就是指针,那假如我们创建一个char类型的字符变量,这个变量存储在内存中,他的地址是怎样的呢?通过sizeof(char),我们知道一个字符变量所占的空间是一个字节,刚好可以放到一个内存单元里,那他的地址自然就对应那一个内存单元的地址,那如果是int类型呢?我们用sizeof(int),发现int所占的空间大小大于1,可能是4,我们就按4来讨论,那他在内存中将会占据四个内存单元,而四个内存单元会分别拥有一个自己的地址,那这个int变量的地址是什么呢?通过VS的内存观察功能,我们能看到这个int变量的地址,实际上是在内存中所占四个内存单元中第一个内存单元的地址,其他类型的变量可以以此类推。

                这里可以简单说下数组,数组的变量其实就是一个地址,这个地址是第一个元素的地址,假如有一个int数组a[5],那么当我们给a[3]赋值时,系统是这样找到a[3]对应的那个int变量的位置的,首先通过a找到第一个元素的位置,然后通过下标3以及这个数组的元素类型int,可以知道第4个元素和第一个元素之间差了多少个内存空间,因此通过计算后可以知道第四个元素所在的地址位置。

                前面都说的是变量对于指针的关系,我们接下来讨论下指针之于变量的关系,我们要用指针,那我们就需要存储的空间,就跟其他变量一样,我们也需要定义一个指针变量,说的通俗点,我们需要一个用来存放地址的变量,我们声明一个指针变量的方法,就是在对应地址所指的空间数据类型后加一个*号,例如某个内存空间存放了一个int变量a,我们想要定义一个指针变量用来存放这个变量a的地址,可以这么定义int   *p=&a,&为取地址运算符,他的结果是对应变量的值,这个语句的意思是,我们定义了一个地址指向存放int类型内存空间的指针变量p,并将变量a的地址作为初始化器,初始化了指针变量p。其他类型也依次类推,数组、函数有所不同,但这里不展开说明。需要注意的一点是,有的人喜欢将星号直接加在int后,而我喜欢加在变量前,在只有一个变量的情况下,问题不大,但是要同时声明多个变量时,会有所影响,思考下面这两句话,想想哪些是指针变量:

int* a,b,c,d;
int *a,*b,*c,*d;

                这里公布下结果,第一个语句里,只有a是指针变量,b,c,d都是普通的int类型变量;而第二个语句里,a,b,c,d均是指针变量。

                所以我们可以知道并不是int*代表指针变量,而是变量前加*代表这个变量时一个指针。

                最后简单说一下指针的应用,日常生活中,我们去朋友家需要知道朋友家的地址,那我们要访问某个变量、函数、数组的时候,自然也可以通过地址找到那些变量所在的内存,我们可以通过间接寻址运算符(解引用操作符)对对应的变量修改,按之前的例子int   *p=&a,我们可以通过*p=20,来给int变量a赋值20,这里的*p=20与a=20是等价的,相当于*p=a。有的人可以会问,那为什么要这么写呢?不直接a=20赋值多舒服阿?

                我们调用函数的时候,传递给函数的值并不是把变量本身给过去,而是函数重新声明了一个相同类型的变量,并将我们传递过去的值作为初始化器,初始化了他声明的新变量。当我们希望我们传给函数变量后,函数内部运算的结果能对我们穿进去的对应变量有所修改,我们就需要传进去一个指针。而后续的回调函数,指针也起到十分重要的作用。

        (三)结构体

                结构体有主要在于定义和访问,定义是通过struct这个关键字定义的,而访问则是应用结构成员访问操作符实现的,这个运算符有两种,一种是在结构体名后加".",另一种是当采用指向某个结构体的指针时,可以在那个指针后加上"->"。两种运算符后再加上对应成员名,就能直接访问对应的结构体成员。

                

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值