c语言给结构体初始化赋值里的变量赋值有点奇怪?

作为IT行业人士需要掌握的最基本的计算机语言—c语言,如今的地位只高不低,c语言掌握程度的高低往往在面试的时候可以完全表现出来,而有些问题是大家平时似乎是知道的,但却不能完整准确地回答上来的,小编当年也遇到了很多此类问题,所以今天大家带来精心整理的C语言经典面试题 ,希望对大家有所帮助。

温馨提示:文章末尾有亮点!

问题一:什么是预编译?何时需要预编译?

答:预编译又称预处理,是整个编译过程最先做的工作,即程序执行前的一些预处理工作。主要处理#开头的指令。如拷贝#include包含的文件代码、替换#define定义的宏、条件编译#if等。.

1、总是使用不经常改动的大型代码体。

2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。

问题二:写一个“标准”宏,这个宏输入两个参数并返回较小的一个

问题三:#与##的作用?

答:#是把宏参数转化为字符串的运算符,##是把两个宏参数连接的运算符。

问题四:如何避免头文件被重复包含?

例如,为避免头文件my_head.h被重复包含,可在其中使用条件编译:

问题一:static关键字的作用?

Static的用途主要有两个,一是用于修饰存储类型使之成为静态存储类型,二是用于修饰链接属性使之成为内部链接属性。

在函数内定义的静态局部变量,该变量存在内存的静态区,所以即使该函数运行结束,静态变量的值不会被销毁,函数下次运行时能仍用到这个值。

在函数外定义的静态变量——静态全局变量,该变量的作用域只能在定义该变量的文件中,不能被其他文件通过extern引用。

静态函数只能在声明它的源文件中使用。

问题二:const关键字的作用?

1.声明常变量,使得指定的变量不能被修改。

const int *ptr; /*ptr为指向整型常量的指针,ptr的值可以修改,但不能修改其所指向的值*/

int *const ptr;/*ptr为指向整型的常量指针,ptr的值不能修改,但可以修改其所指向的值*/

2.修饰函数形参,使得形参在函数内不能被修改,表示输入参数。

3.修饰函数返回值,使得函数的返回值不能被修改。

问题三:volatile关键字的作用?

volatile指定的关键字可能被系统、硬件、进程/线程改变,强制编译器每次从内存中取得该变量的值,而不是从被优化后的寄存器中读取。例子:硬件时钟;多线程中被多个任务共享的变量等。

问题四:extern关键字的作用?

1.用于修饰变量或函数,表明该变量或函数都是在别的文件中定义的,提示编译器在其他文件中寻找定义。

其中,在函数的声明带有关键字extern,仅仅是暗示这个函数可能在别的源文件中定义,没有其他作用。如:

此时,展开头文件A_MODULE.h后,为

extern int func(int a, int b);/*虽然暗示可能在别的源文件中定义,但又在本文件中定义,所以extern并没有起到什么作用,但也不会产生错误*/

extern “c”的作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的编译方式进行编译,而不是C++的。

C++作为一种与C兼容的语言,保留了一部分面向过程语言的特点,如可以定义不属于任何类的全局变量和函数,但C++毕竟是一种面向对象的语言,为了支持函数的重载,对函数的编译方式与C的不同。例如,在C++中,对函数void fun(int,int)编译后的名称可能是_fun_int_int,而C中没有重载机制,一般直接利用函数名来指定编译后函数的名称,如上面的函数编译后的名称可能是_fun。

这样问题就来了,如果在C++中调用的函数如上例中的fun(1,2)是用C语言在源文件a_module.c中实现和编译的,那么函数fun在目标文件a_module.obj中的函数名为_fun,而C++在源文件b_module.cpp通过调用其对外提供的头文件a_module.h引用后,调用fun,则直接以C++的编译方式来编译,使得fun编译后在目标文件b_module.obj的名称为_fun_int_int,这样在链接的时候,因为_fun_int_int的函数在目标文件a_module.obj中不存在,导致了链接错误。

解决方法是让b_module.cpp知道函数fun是用C语言实现和编译了,在调用的时候,采用与C语言一样的方式来编译。该方法可以通过extern “C”来实现(具体用法见下面)。一般,在用C语言实现函数的时候,要考虑到这个函数可能会被C++程序调用,所以在设计头文件时,应该这样声明头文件:

/*这样通过extern “C”告诉C++编译器,extern “C”{}里包含的函数都用C的方式来编译*/

2. 可以是复合语句, 相当于复合语句中的声明都加了extern "C"

3.可以包含头文件,相当于头文件中的声明都加了extern"C"

5. 如果函数有多个声明,可以都加extern"C", 也可以只出现在第一次声明中,后面的声明会接受第一个链接指示符的规则。

问题五:sizeof关键字的作用?

sizeof是在编译阶段处理,且不能被编译为机器码。sizeof的结果等于对象或类型所占的内存字节数。sizeof的返回值类型为size_t。

注意:不能对结构体中的位域成员使用sizeof

问题一:结构体的赋值?

C语言中对结构体变量的赋值或者在初始化或者在定义后按字段赋值。

GNU C中可使用另外一种方式:

方式2:定义变量后按字段赋值

而当你使用初始化的方式来赋值时,如x = {‘A’,1};则出错。

方式3:结构变量间的赋值

问题二:结构体变量如何比较?

答:虽然结构体变量之间可以通过=直接赋值,但不同通过比较符如==来比较,因为比较符只作用于基本数据类型。这个时候,只能通过int memcmp(const void *s1, const void *s2, size_t n);来进行内存上的比较。

位域是一个或多个位的字段,不同长度的字段(如声明为unsigned int类型)存储于一个或多个其所声明类型的变量中(如整型变量中)。

位域的特点:字段可以不命名,如unsignedint :1;可用来填充;unsigned int :0; 0宽度用来强制在下一个整型(因此处是unsigned int类型)边界上对齐。

sizeof(s1)等于3。因为一个位域字段必须存储在其位域类型的一个单元所占空间中,不能横跨两个该位域类型的单元。也就是说,当某个位域字段正处于两个该位域类型的单元中间时,只使用第二个单元,第一个单元剩余的bit位置补(pad)0。

unsigned intb:2;/*前一个整型变量只剩下1个bit,容不下2个bit,所以只能存放在下一个整型变量*/

1.有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。这样节省存储空间,而且处理简便。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

2.可以很方便的利用位域把一个变量给按位分解。比如只需要4个大小在0到3的随即数,就可以只rand()一次,然后每个位域取2个二进制位即可,省时省空间。

不同系统对位域的处理可能有不同的结果,如位段成员在内存中是从左向右分配的还是从右向左分配的,所以位域的使用不利于程序的可移植性。

问题四:结构体成员数组大小为0

结构体数组成员的大小为0是GNU C的一个特性。好处是可以在结构体中分配不定长的大小。如

问题一:函数参数入栈顺序

C语言函数参数入栈顺序是从右向左的,这是由编译器决定的,更具体的说是函数调用约定决定了参数的入栈顺序。C语言采用是函数调用约定是__cdecl的,所以对于函数的声明,完整的形式是:int __cdecl func(int a, int b);

问题二:inline内联函数

inline关键字仅仅是建议编译器做内联展开处理,即是将函数直接嵌入调用程序的主体,省去了调用/返回指令。

1) malloc与free是C/C++语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

我们不要企图用malloc/free来完成动态对象的内存管理,应该用new/delete。由于内部数据类型的“对象”没有构造与析构的过程,对它们而言malloc/free和new/delete是等价的。

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,结果也会导致程序出错,但是该程序的可读性很差。所以new/delete必须配对使用,malloc/free也一样。

答:如果请求的长度为0,则标准C语言函数malloc返回一个null指针或不能用于访问对象的非null指针,该指针能被free安全使用。

可变参数列表是通过宏来实现的,这些宏定义在stdarg.h头文件,它是标准库的一部分。这个头文件声明了一个类型va_list和三个宏:va_start、va_arg和va_end。

宏va_start的第一个参数是va_list类型的变量,第二个参数是省略号前最后一个有名字的参数,功能是初始化va_list类型的变量,将其值设置为可变参数的第一个变量。

宏va_arg的第一个参数是va_list类型的变量,第二个参数是参数列表的下一个参数的类型。va_arg返回va_list变量的值,并使该变量指向下一个可变参数。

宏va_end是在va_arg访问完最后一个可变参数之后调用的。

问题一:实现printf函数

答:ASSERT()是一个调试程序时经常使用的宏,在程序运行时它计算括号内的表达式,如果表达式为FALSE (0), 程序将报告错误,并终止执行。如果表达式不为0,则继续执行后面的语句。这个宏通常原来判断程序中是否出现了明显非法的数据,如果出现了终止程序以免导致严重后果,同时也便于查找错误。例如,变量n在程序中不应该为0,如果为0可能导致错误,你可以这样写程序:

ASSERT只有在Debug版本中才有效,如果编译为Release版本则被忽略。

assert()的功能类似,它是ANSI C标准中规定的函数,它与ASSERT的一个重要区别是可以用在Release版本中。

答:系统的暂停程序,按任意键继续,屏幕会打印,"按任意键继续。。。。。"省去了使用getchar();

问题三:请问C++的类和C里面的struct有什么区别?

答:c++中的类具有成员保护功能,并且具有继承,多态这类oo特点,而c里的struct没有。c里面的struct没有成员函数,不能继承,派生等等.

解答:字符串str1有11个字节(包括末尾的结束符''),而string只有10个字节,故而strcpy会导致数组string越界。

解答:因为str1没有结束符'',故而strcpy复制的字符数不确定。strcpy源码如下:

解答:C语言中的函数参数为传值参数,在函数内对形参的修改并不能改变对应实参的值。故而调用GetMemory后,str仍为NULL。

解答:GetMemory中,p为局部变量,在函数返回后,该局部变量被回收。故而str仍为NULL

解答:试题6避免了试题4的问题,但在GetMemory内,未对*p为NULL情况的判断。当*p不为NULL时,在printf后,也未对malloc的空间进行free

... //省略的其它语句

解答:未对str为NULL的情况的判断,在free(str)后,str未设置为NULL,可能变成一个野指针(后面对str的操作可能会导致踩内存)。

解答:在swap函数中,p是个野指针,可能指向系统区,导致程序运行的崩溃。故而,程序应改为:

今天帮师姐调一个程序的BUG,师姐的程序中有个结构体直接赋值的语句,在我印象中结构体好像是不能直接赋值的,正如数组不能直接赋值那样,我怀疑这个地方有问题,但最后证明并不是这个问题。那么就总结一下C语言中结构体赋值的问题吧:

可以从结果上看出,结构体直接赋值在C语言下是可行的,我们看看struct_assign()函数的汇编实现,从而从底层看看C语言是如何实现两个结构体之间的赋值操作的:

这段汇编比较简单,由于结构体的对齐的特性,sizeof(srtruct Foo)=16,通过四次movl操作将foo1的结构体内容拷贝到结构体foo2中。从汇编上看出,结构体赋值,采用的类似于memcpy这种形式,而不是逐个字段的拷贝。

如果结构体中含有其它复杂数据类型呢,例如数组、指针、结构体等,从上面的汇编实现可以看出,只要两个结构体类型相同,就可以实现赋值,如下例:

可以看出结果和我们想象的是一样的。再次验证结构体的赋值,是直接结构体的内存的拷贝!但正是这个问题,如上面的实例,foo1 和 foo2 中p_c 指针都是指向我们申请的一块大小为4个字节的内存区域,这里注意的是,结构体的拷贝只是浅拷贝,即指针p_c的赋值并不会导致再申请一块内存区域,让foo2的p_c指向它。那么,如果释放掉foo1中的p_c指向的内存,此时foo2中p_c变成野指针,这是对foo2的p_c操作就会出现一些不可预见的问题!在C++中引入了一种可以允许用户重载结构体赋值操作运算,那么我们就可以根据语义重载赋值操作。

在维基百科上的解释是:

二等公民不是一个正式的术语,用来描述一个社会体系内对一部分人的歧视或对外来人口的政治限制,即使他们作为一个公民或合法居民的地位。 二等公民虽然不一定是奴隶或罪犯,但他们只享有有限的合法权利、公民权利和经济机会,并经常受到虐待或忽视。法律无视二等公民,不向他们提供保护,甚至在制订法律时可能会根本不考虑他们的利益。划分出二等公民的行为,普遍被视为一种侵犯人权的行为。 典型的二等公民所面临的障碍包括但不仅限于(缺乏或丧失表决权):权利被剥夺,限制民事或军事服务(不包括任何情况下的征兵),以及限制,语言,宗教,教育,行动和结社的自由,武器的所有权,婚姻,性别认同和表达,住房和财产所有权 。

从词条上解释可以看出二等公民与一等公民在权利上是有差别的,这个词很有意思作为计算机专业术语,其含义也有异曲同工之妙!同样我们看看维基百科对计算机的术语”"(一等公民)的定义,一般要满足以下几点,

对比着上面的定义来看C语言数组,数组作为一个函数的参数传递时,退化成一个指针; 同时,数组无法作为函数的返回值; 也许让数组更不服气的是,数组之间不能直接赋值操作,如下面的操作就是非法的:

但是如果数组包装在结构体中,那么就能进行赋值了!相比之下,结构体可以作为函数参数和返回值,这就是一等公民的待遇!至于为什么数组必须是二等公民,这是有历史原因的,大家可以参考来看,有时间这块内容我再补上!



当我们将一个程序交给CPU去执行的时候,CPU只会执行main函数中的代码,别的地方的代码是不会执行的,因此如果想要在CPU中执行程序就必须要在main函数中从上到下一句一句执行代码,并且只有在上一句执行完毕之后才会执行下一句。
mian是程序的入口,当mian函数中的代码执行完毕之后就会自动结束,所以也是出口。

CPU : 中央处理器,处理数据的,负责计算,协调其他硬件相互和谐的工作。

内存 :存储数据 ,临时,效率高,通过电路存储,电子式。

硬盘 :存储数据 ,永久存储,效率低,效率和转速有关,机械式。

3. 程序是如何运行的

程序本质就是一对指令,程序存储在硬盘之中,当双击点开之后,CPU会先将程序复制到内存之中,然后CPU再去读取内存中程序的指令。这是因为在内存中CPU的读取效率更高。
例如:播放器播放电影,播放时是运行在内存中的,但是电影文件确实保存在硬盘中的,当双击电影打开播放器,播放器运行在内存中,然后播放器在去分段的读取电影文件。

或者英雄联盟这样的大型游戏也不是将整个游戏拷贝到内存中,而是只将exe文件拷贝到CPU中,然后需要什么文件就加载哪些文件,不用的就会释放掉。

    程序自己是否要把这些数据存储起来,对于用户操作产生的数据,或者一会儿要用的数据可以存储起来,以便以后显示方便快捷。 存储到内存中,因为程序自己就在内存当中
  1. 如何将数据存储在内存之中?
    先在内存中找一个位置,将数据放到这个位置中,当想要使用数据的时候,只要找到这个位置就可以了。
    而每一个位置在内存中都有一个独一无二地址,16进制表示,0x开头。通过这个地址找到这个位置,然而地址非常难记,所以给位置起个别名,通过别名找到位置。
    因此在开辟空间的同时,需要指定空间的别名和类型,而变量就是内存中用来存储数据的空间,那么变量名就是变量所代表的那块空间的别名。所以,变量的本质就是内存中存储数据的那块儿空间。
    声明一个变量,实际上就是在内存中开辟一块指定类型和别名的空间

5. 内存中的五大区域

为什么要分区个区域?虽然每一个区域都是用来存储数据的,但是不同的数据存储在不同的区域,这样不仅方便系统的管理,也可以使系统更快,更明确的找到要找的地址。

  1. 栈 - 专门用来存储局部变量,所有的局部变量都是声明在栈区域当中。
  1. 堆 - 允许程序员手动的从堆申请空间来使用。程序员可以自己申请指定字节数的空间。
  2. BSS段 - 用来存储未初始化的全局变量和静态变量,声明一个全局变量,如果我们没有初始化,在程序运行最开始的时候,这个变量没有初始化时是存储在BSS段,初始化之后,全局变量和静态变量就会被放到常量区。
  3. 数据段/常量区 - 用来存储已经初始化的全局变量、静态变量,还有常量数据
  4. 代码段 - 用来存储程序的代码/指令。

注意:我们堆中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束
如何在堆区申请指定字节数的字节空间呢?C语言提供了三个函数用来申请空间。这三个函数声明在<stdlib.h>的系统头文件中。

表示在堆内存中申请参数个连续的字节空间,返回值是void *表示没有类型的指针。并且返回的是创建的空间中的第一个字节的地址。
那么我们应该使用什么类型的指针变量来保存malloc()返回的地址?
用什么类型去接受,那要看你想要如何去操作申请的这些字节空间。如果你想要4个字节4个字节去操作那么就要用 int * 类型去接收,如果想8个字节8个字节的去错做那么就用 double * 类型去接收。

所以: int *p = malloc(24); 就相当于在堆内存中创建了一个长度为6的整型数组

1.在堆区申请的字节空间是从低地址向高地址申请,每次申请的字节地址都是从0开始的,并且每次申请的空间不一定是连续的。但是每一次申请的指定个字节,这些字节一定是连续的。
2.在堆区申请的字节里面也是有值的,值是垃圾值,并且值不会自动清零。
3.在向堆区申请字节空间的时候,有可能会申请失败,如果申请失败,返回的指针就是NULL值,所以我们在申请空间之后,最好进行判断 if(p1) 看是否申请成功。
4.申请的空间使用完毕之后,一定要进行释放 free(p1); 如果没有free(),那么需要等程序结束之后这些空间才会被释放。

calloc() 作用:向堆区申请指定字节数的空间。
参数2.每一个单位的字节数
同样有可能申请失败。与malloc()的优势,calloc()申请的字节,申请完之后,系统会将申请到的空间自动清零

当我们申请的字节空间不够用的时候,我们可以使用realloc() 扩容。
一般会在我们申请的空间后面扩容,但是如果我们申请的空间后面被占用了,或者不够我们扩容的空间,就会重新去寻找一块新足够的空间申请,并将原来的数据拷贝过来,原来的空间将被释放。

注意:我们只能操作我们申请到的字节空间,如果贸然操作其他字节空间,很有可能修改掉系统的数据,造成严重问题。

作用: 将字符串数据输出到指定的流中。
流: 标准输出流->控制台。文件流 --> 磁盘上的文件。
使用格式: fputs(要输出的字符串,指定的流);
1). 使用fputs函数将字符串数据输出到标准输出流,也就是控制台
2). 将字符串存储到文件中.
a. 要先声明1个文件指针,指向磁盘上的文件。使用fopen函数可以创建1个指向文件的指针。

fopen函数的两个参数:
第1个参数: 文件的路径,代表创建的指针就指向这个文件。
第2个参数: 操作文件的模式,你要对这个文件做什么操作,必须要告诉他。
当操作模式是"w"的时候,如果文件不存在, 就会创建这个文件,如果文件存在,就会将原来的文件替换掉。
当操作模式是"a"的时候,如果文件存在则追加。如果不存在就创建这个文件。

b. 使用fputs函数将字符串写入到指定的文件流中。
c. 写完之后,一定要记得使用fclose()函数将这个文件关闭。

作用: 从指定的流中读取字符串。
这个流可以是标准输入流-->控制台,也可以是文件流。
1). 使用fgets函数从标准输入流中读取数据。
使用fgets函数从控制台接收用户输入字符串,scanf函数gets函数也可以实现这个功能。

b. 输入的空格会被认为结束. b. 空格也会一并接收.
fgets(要将字符串存储到哪1个数组中,最多接收多少个长度的字符串,指定流);
第2个参数: 如果参数为n 那么函数最多就接收n-1个长度的字符串,这个参数一般情况下和第1个参数数组的长度一致。
第3个参数:流,stdin: 代表标准输入流,也就是键盘流从控制台输入。

为什么fgets函数是安全的?
1. 如果输入的字符串的长度大于等于了第2个参数n,只会接收前面的n-1个,然后最后1个自动是'\0'。这样,就不会崩溃。
2. 如果输入的字符串的长度刚好等于n-1那就是刚好的。
3. 如果输入的字符串的长度小于了n-1,那么就会将我们最后输入的换行字符'\n'一并的接收。然后后面才是'\0'结束符。

2). 使用fgets函数从文件流中读取数据:

就是读取磁盘上文件的内容.
// 1. 创建1个读取文件的文件流.
// 2. 准备1个字符数组.准备存储读取到的字符串数据.
// 3. 使用fgets函数从指定的文件流中读取.

8. const修饰基本数据类型的变量

const是1个关键字,是来修饰变量的,也就是说在声明变量的同时,可以使用const关键字来修饰。
一般情况下来说,被const修饰的变量具备一定程度上的不可变性,被const修饰的变量我们叫做只读变量。

这个时候.num变量的值只能去取值,而不能去修改. 数组的元素的值不能修改. 无法通过p1指针去修改指针指向的变量的值,但是如果直接操作变量这是可以的,并且指针变量的值可以改,可以把另外1个变量的地址赋值给这个指针。 p1的值不能修改,但是可以通过p1去修改p1指向的变量的值。 既不能修改p1的值,也不能通过p1去修改p1指向的变量的值。 被const修饰的变量,是只读变量,只能取值而不能改值。所以,const变量的值,至始至终都不会发生变化。因此当某些数据是固定的,在整个程序运行期间都不会发生变化,并且你也不允许别人去修改时,可以使用const来修饰这个变量。 3. 当函数的参数是1个指针的时候,函数的内部是有可能会修改实参变量的值的,那么这个时候,可以使用const修饰指针参数,这样函数内部只会使用我们的值

不同的数据类型的变量是用来保存不同类型的数据的。而结构体是我们自己定义的数据类型。并指定这个数据类型的变量由哪些小的变量和成的。

这代表我们新创建了一个数据类型,这个数据类型的名称叫做 struct Student
这个新类型是由1个char * 类型,一个int 类型,一个float类型的小的标量联合而成的。然而只有类型是不够的,还需要根据类型声明变量。

声明结构体类型的变量:

** 结构体变量的初始化**
意义:为结构体变量中的小变量赋值。严格意义上是将地址赋值给小变量。结构体变量中的小变量就叫做结构体的成员。

初始化语法 使用点语法。
结构体变量名称.成员 = 数据;

当要保存一个数据,但是发现这个数据需要由其他小变量组成的,这个时候先使用结构体类自定义这个数据类型由哪些小变量合成的,然后在根据这个结构体类型声明变量,来保存数据。

  1. 一定要先声明结构体类型,然后在声明结构体变量。

  2. 结构体变量也是变量所以可以批量声明。

  3. 定义结构体名称要求每一个单词的首字母大写。

  4. 可以在声明结构体类型的时候声明结构体变量。

  5. // 匿名结构体只能在声明结构体的同时创建变量,并且不能单独的声明变量
  1. 先声明变量,在使用点语法一个一个赋值。
  2. 在声明结构体变量的同时,就为结构体变量的成员初始化。(最常用)
  3. 只初始化部分成员,按顺序。

结构体变量成员的默认值
声明一个结构体变量,如果没有给结构体变量成员赋值,那么成员是有值的,是垃圾值。只要在声明结构体变量的同时,为一个成员变量初始化,整个结构体就会自动初始化为0,就不在是垃圾值了。

一般情况下结构体类型都是定义在函数外面,已让所有函数都可以使用。

结构体变量之间的相互赋值
相同结构体类型的结构体变量是可以赋值的。
结构体变量之间赋值的原理:
将结构体变量中的每一个成员的值,拷贝过来复制一份,然后重新赋值给目标结构体变量中对应的成员。结构体变量之间的赋值是值传递。

struct 结构体类型名称 数组名[数组长度]; 
可以存储5个strut 结构体类型名称 的结构体
或者直接在声明结构体数组的时候,为结构体赋值。
使用sizeof计算出数组占用的总字节数/ 每一个元素占用的字节数
声明了一个pStu指针变量,这个指针变量的类型是struct Student *。

使用。可以使用指针间接的访问结构体变量。

(*结构体指针名).成员 结构体指针名->成员

结构体是可以嵌套的,在一个结构体内部声明另外一个结构体即可。

结构体是自定义的数据类型,当然可以作为参数,结构体作为参数传值是值传递,如果想要在函数中修改结构体变量的值,可以使用结构体指针。

结构题类型完全可以作为函数的返回值,在返回的时候直接将结构体变量返回即可。如果返回结构体变量的地址,需要将结构体创建在堆区。

变量的取值只能是指定的几个值当中的任意一个,除此之外其他不行,需要自己定义具备限定取值的类型。
作用:支持先创建一种数据类型,这个数据类型的变量的取值被限定。

这个数据类型的名称叫做 **enum Type **。可以声明这个类型的变量,这个变量中就只能存储这其中指定的任意一个。

enum 枚举类型名称 变量名 = 枚举类型限定的取值之一。

一般定义在函数外,每一个枚举值都对应一个整形数,默认为0,依次递增。枚举类型的变量,无论什么类型 都占据4个字节。而枚举变量中真正存储的是,枚举值对应的整形的数。所以使用%d输出枚举的值。

所以也可以直接为枚举变量赋值整形变量。但是一般不建议这么做 ,可读性降低。命名规范 首字母大写,每一个单词的首字母大写
枚举值名称以枚举类型名开头

作用: 为一个已经存在的数据类型取别名。
因此当数据类型很长的时候既可以为这个数据类型取一个短一点的别名。

1.声明结构体类型的同时给结构体区别名 2.最常用是为声明匿名结构体的同时 取一个短别名。

C语言从编写到编译、链接、执行的流程

  1. 先执行原文件中的预处理指令,如果有文件包含指令,就将文件的内容拷贝到写指令的地方。
  2. 检查语法是否符合规范,符合就生成.o目标文件,就是.c 对应的二进制指令。如果不符合语法规范,就报错不生成.o目标文件。为.o的目标文件添加启动代码
    告诉编译器要调用的函数在什么地方
    调用的时候去正确的地方找实现
  3. 链接成功以后.out文件运行即可。

预处理指令以#开头,并且都是在编译之前执行。

  1. 宏定义:可以将一段代码定义为一个标识,使用这个标识就代表这段代码。
  2. 条件编译指令:只编译指定的C代码为二进制指令。

会将C代码中使用宏名的地方替换成宏值 过程叫做宏替换

宏值可以是任意语句,定义宏的时候,并不会去检查与法,只有当完成了宏替换的时候,才会去检查替换以后的代码是否符合语法规范。
如果宏值是一个表达式,那么宏值并不是表达式的值,而是表达式本身。
如果宏值当中包括一个变量名,那么在使用这个宏之前必须保证这个变量已经存在。
无法通过赋值符号位宏赋值。因为宏根本就不是变量。

从定义宏的地方开始,后面的所有地方都可以使用这个宏。就算这个宏定义在这个大括弧里面,在这个后面,哪怕是大括弧的后面都可以使用。
默认情况下,宏从定义的地方一直到文件结束都可以使用,#undef可以让宏提前失效

解除宏定义,之后宏就不可以使用了 体现实效

字符串优先,也就是字符串中不会识别宏。系统不会认为是一个宏,而认为是字符串的一部分。
宏的层层替换。一个宏值中可以使用另外一个宏名。

#define 预处理指令 在预编译的时候会把宏明替换成宏值,typedef运行的时候才会执行。
#define可以将任意的C代码取一个表示名, typedef只能为数据类型取名字。

在定义宏的时候,宏名是可以带参数的。在这个宏值当中,可以直接使用这个参数。

// 如果使用的宏有参数,就必须在使用的时候为宏传值。

先将参数赋值,然后在将宏值里面用到参数的地方替换为值,最后宏替换,将值替换为宏名。

  1. 宏不是函数,所以宏的参数不需要添加类型说明。
  2. 我们在定义宏的时候,编译器是如何区分宏名和宏值的。
    #define 宏名 宏值 宏名中不可以有空格,与参数之间也不可以有空格。
  3. 为带参数的宏传值的时候,是本色传递,如果传递一个变量,并不是传递这个变量的值,而是直接传递的就是这个变量的串。
  4. 宏值一旦换行就认为宏定义结束了,需要使用 \ 来拼接宏
  5. 宏只适合于少量的代码。
  1. 预处理指令, 在预编译阶段执行。
  2. 作用:默认情况下,我们所有的C代码都会被编译为二进制代码,条件编译指令的作用,可以让编译器只编译部分的代码。
  1. 条件编译指令是一个预处理指令,在预处理阶段执行,而if语句是C代码,在程序运行的时候执行。
  2. if语句无论如何全都要被编译为二进制指令,条件编译指令:只会将符合条件的C代码编译为二进制指令。
  3. 条件编译指令参数必须为宏。
如果定义了宏名的宏 就编译其中的代码 如果没有定义宏名就来到这里。

C语言当中的两个关键字,是用来修饰变量和函数的。
如果都没有修饰 默认是 extern 。

  1. static修饰局部变量,那么这个变量就叫做静态变量,静态变量不在存储在栈区,而是存储在常量区中,当函数执行完毕之后,这个静态变量不会被回收。
    当第一次执行这个函数的时候,就会将这个静态变量声明在常量区,第二次去执行这个函数的时候,声明静态变量的这句话就不会在执行了,而是直接略过,直接使用静态变量的值。
    所以static修饰的静态变量,函数结束不会被回收,仍然存在,函数无论执行多少次,这个静态变量只有一份。
    extern不能修饰局部变量。

  2. 一个全局变量,最完整的步骤也应该分为两步,1.先写全局变量的声明,只定义而不赋值。2.在写全局变量的定义,定义全局变量并初始化。
    全局变量可以只有声明,如果这样的话,那么这个全局变量的值背会编译器自动的去实现,会将这个全局变量自动初始化为0。
    全局变量也可以只有定义而没有声明,但是这个时候,这个全局变量的定义必须要在使用全局变量的函数的前面。
    全局变量的声明要写在.h文件中,全局变量的实现要写在.c文件中。
    如果将全局变量定义在模块之中,这个全局变量就必须要使用static或者extern来修饰
    static修饰全局变量,这个全局变量只能在当前 模块访问。
    extern修饰全局变量,这个全局变量就可以跨模块访问。

  3. static修饰函数 函数只可以在当前模块访问
    extern修饰函数 那么函数可以跨模块调用

文中如果有不对的地方欢迎指出。我是xx_cc,一只长大很久但还没有二够的家伙。

本文已在版权印备案,如需转载请访问版权印。

我要回帖

更多关于 结构体初始化赋值 的文章

 

随机推荐