C语言基础知识

来源:互联网 发布:mathematica mac 编辑:程序博客网 时间:2024/06/06 21:17

Volatile关键词如果一个基本变量被volatile修饰,编译器将不会把它保存到寄存器中,而是每一次都去访问内存中实际保存该变量的位置上取值

#define DEF_8BIT_REG_AT(NAME,ADDRESS)  volatile unsigned char  NAME  @ADDRESS

例:

DEF_8BIT_REG_AT(PA_ODR,0x5000); //PA_ODR  ( 变量名0x5000  (地址绑定起来

===========================================================

无符号整型即unsignedint的表示范围不是04294967295吗?负数超出了这一范围为什么还能表示?比如说—2unsigned int表示就是1111111111111110(十五个一)。请各位高手帮帮忙~~解释清楚为什么?

答:

04294967295按顺序排列成一个环形,则0两边的数就是14294967295,如果无符号整型在使用中没有超出范围,那么结果都是正确的,如果超出了范围,程序也会给出一个结果但这个结果是错误的,错误的结果也是有规律的,比如说,无符号整型所表示的最小的数是0,如果在使用中出现了-1,那么程序给出的(错误)结果就是环形中0旁边的数,显然不会是1,只能是4294967295。依此环形类推,-2就是4294967294,转换成二进制就是1111111111111110(十五个一)。

===========================================================

负数在计算机中的表示方法:

1在计算机中,负数以其正值的补码形式表达(补码 =反码 + 1)

2:比如原来数据为-5,先不管其符号,将原数据正值(二进制)500000101,取反后的反码为11111010.

3:对反码加1.  11111010+1=11111011,此值即为-5的二进制表示。

===========================================================

在有符号数中二进制最高位是1的就是负数, 0就是正数

===========================================================

负数:原码 = 补码各位取反+1

注意:这里所进行的取反操作是针对符号位之外的其它位,也就是说,不应该对符号位取反,如果你这么做了,将得不到预期的结果
正数:原码= 补码

===========================================================

/*求绝对值函数*/

u16 abs(s16 temp)

{

if(temp < 0)

{

temp = ~temp +1; //这个取反包括符号位

}

return (temp);

}

扩展:

(-1)>>1依然等于-1

(-1)/2 = 0;

答:(-1)在计算机中已补码形式保存(FF),对于有符号数,最高位是会被复制的,也就是它是1,则移位后还是填充1,对于-1,它所有的位都是1,因此你无论移多少位,它还是-1

===========================================================int *p

int a[4];

p = a;    //此时数组名被当成该数组下标为0的元素的指针

p = &a;   //&a是一个指向数组的指针,p是一个指向整形数的指针,故他们的类型不匹配

总结:数组名一般代表该数组下标为0的元素的指针,只有一种情况除外,即:数组名被用作操作符sizeof的参数这一情况,sizeof修饰数组名表示整个数组的大小,而不是指向该数组的元素的指针的大小

===========================================================C程序存储空间布局:

C程序一直由下列部分组成:

1:正文段——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令
2:初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
3:非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0。
4:栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
5:堆——动态存储分。

|----------- |
|                |
|----------- |
|    
栈      |
|----------- |
|      |        |
|      |        |
|      |        |
|      |        |
|      |        |
|      |       |
|----------- |
|    
堆      |
|----------- |
|  未初始化  |
|----------- |
|   初始化   |
|----------- |
|   正文段   |
|-----------    |

 面向过程程序设计中的static:

1. 全局静态变量:在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量。

1:内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

2:初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

3:作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。

定义全局静态变量的好处:

<1>不会被其他文件所访问,修改

<2>其他文件中可以使用相同名字的变量,不会发生冲突

看下面关于作用域的程序:
//teststatic1.c
voiddisplay();
externintn;
int main()
{
     n = 20;
     printf("%d/n",n);
     display();
     return 0;
}
 
//teststatic2.c 
staticintn; //
定义全局静态变量,自动初始化为0,仅在本文件中可见
void display()
{
    n++;
    printf("%d/n",n);
}
 

文件分别编译通过,但link的时候teststatic1.c中的变量n找不到定义,产生错误。
===========================================================

 局部静态变量在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量。

1:内存中的位置:静态存储区

2:初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

3:作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

 

注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。

当static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。

===========================================================

静态函数:在函数的返回类型前加上关键字static,函数就被定义成为静态函数。函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

定义静态函数的好处:

<1> 其他文件中可以定义相同名字的函数,不会发生冲突

<2> 静态函数不能被其他文件所用。

 例如:
//teststatic1.c
void display();
static void staticdis(); 
int main()
{
    display();
    staticdis();
    renturn 0;
}
 
//teststatic2.c
void display()
{
    staticdis();
    printf("display() has been called /n");
}
 
static void staticdis()
{
    printf("staticDis() has been called/n");
}


文件分别编译通过,但是连接的时候找不到函数staticdis()的定义,产生错误。
 
===========================================================

 
存储说明符auto,register,extern,static,对应两种存储期:自动存储期和静态存储期。
auto和register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。

关键字extern和static用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。

由于static变量的以上特性,可实现一些特定功能。

1:统计次数功能

声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。

 

代码如下:
void count();
int main()
{
   int i;
   for (i = 1; i <= 3; i++)
         count();
    return 0;
}
void count()
{
   static num = 0;
   num++;
   printf(" I have been called %d",num,"times/n");
}

输出结果为:
I have been called 1 times.

 ==========================================================

 

C语言程序可以看成由一系列外部对象构成,这些外部对象可能是变量或函数。而内部变量是指定义在函数内部的函数参数及变量。外部变量定义在函数之外,因此可以在许多函数中使用。由于C语言不允许在一个函数中定义其它函数,因此函数本身只能是外部的
由于C语言代码是以文件为单位来组织的,在一个源程序所有源文件中,一个外部变量或函数只能在某个文件中定义一次,而其它文件可以通过extern声明来访问它(定义外部变量或函数的源文件中也可以包含对该外部变量的extern声明)。 static则可以限定变量或函数为静态存储。如果用static限定外部变量与函数,则可以将该对象的作用域限定为被编译源文件的剩余部分。通过static限定外部对象,可以达到隐藏外部对象的目的。因而,static限定的变量或函数不会和同一程序中其它文件中同名的相冲突。如果用static限定内部变量,则该变量从程序一开始就拥有内存,不会随其所在函数的调用和退出而分配和消失。

 C语言中使用静态函数的好处

1静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。 

2关键字“static”,译成中文就是静态的,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。 使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。 

 

 

c语言中static的语义
1.static变量:
1.局部
a.静态局部变量在函数内定义,生存期为整个源程序,但作用域与自动变量相同,只能在定义该变量的函数内使用。退出该函数后, 尽管该变量还继续存在,但不能使用它。
b.
对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。
2.全局
全局变量本身就是静态存储方式, 静态全局变量当然也是静态存储方式。但是他们的作用域,非静态全局 变量的作用域是整个源程序(多个源文件可以共同使用); 而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。
2.static函数(也叫内部函数)
只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用。区别于一般的非静态函数(外部函数) 
static
c里面可以用来修饰变量,也可以用来修饰函数。

 

变量在c里面可分为存在全局数据区、栈和堆里。其实我们平时所说的堆栈是栈而不包含对,不要弄混。
int a ;
main()

{

int b ; 

int c* = (int *)malloc(sizeof(int));
 }
 a
是全局变量,b是栈变量,c是堆变量。
 ========================================================== 

 

关于Static的理解

关于static变量,请选择下面所有说法正确的内容:

A、若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;

B、若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;

C、设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;

D、静态全局变量过大,可那会导致堆栈溢出。 

 

答案与分析:

D:静态变量放在程序的全局数据区,而不是在堆栈中分配,所以不可能导致堆栈溢出,

答案:ABC

 ========================================================== 

编译器的工作原理:

预处理(Pre-Processing)
编译(Compiling)
汇编(Assembling)
链接(Linking)

编译器是按照编译单元进行编译的,所谓的编译单元,是指一个.c文件以及它所include的所有.h文件.最直观的理解就是一个文件,一个工程中可以包含很多文件,其中有一个程序的入口点,即我们通常所说的main()函数(当然也可以没有这个函数,程序照样能启动).在没有这个程序入口点的情况下,编译单元只生成目标文件object file(.o文件,windows下叫做.obj).

在编译阶段只是生成各自的.o文件.这个阶段不和其它的文件发生任何的关系.
include这个预处理指令发生在预处理阶段(早先编译阶段,只是编译器的一个前驱处理程序).

下面我们主要聊聊编译器的处理过程.(我想初学者有疑问的正在于此,即是对于编译过程.h .c(.cpp)的变化不太了解,)
例子如下:
//a.h

class   A
{
pubic:
      int   f(int   t);
};

//a.cpp
#include   "a.h"
int   A::f(int   t)
{
    return   t;
}

//main.cpp
#include   "a.h"
void   main()
{
      A   a;
      a.f(3);
}
在预处理阶段
:预处理器看到#include "文件名"就把这个文件读进来,比如它编译main.cpp,看到#include   "a.h",它就把a.h的内容读进来,它知道了,有一类A,包含一个成员函数f,这个函数接受一个int型的参数,返回一个int型的值。再往下编译很容易就把A   a这行读懂了,它知道是要拿A这个类在栈上生成一个对象。再往下,它知道了下面要调用A的成员函数f了,参数是3,由于它知道这个函数要一个整形数用参数,这个3正好匹配,那就正好把它放到栈上,生成一条调用f(int)函数的指令,至于这个f(int)函数到底在哪里,它不知道,它留着空,链接时再解决。它还知道f(int)函数要返回一个int,所以也许它也为这一点做好了准备(在例子中,我们没用这个返回值,也许它就不处理)。再往下到文件末尾了main.cpp编译好了,生成了main.obj整个编译过程中根本就不需要知道a.cpp的内容
同理,编译器再编译a.cpp,把f()函数编译好,编译a.cpp时,它也不用管别的,把f()编译好就行了。生成了a.obj
最后一步就是链接的阶段:链接器把项目中所有.cpp生成的所有.obj链接起来,
在这一步中,它就明确了f(int)函数的实现所在的地址,把main.obj中空着的这个地址位置填上正确的地址。最终生成了可执行文件main.exe

明白了吗?不明白那就多说几句了,我们在学编译原理的时候都知道,编译器是分阶段进行的,每一个阶段将源程序从一种表示转换成另一种表示,一般情况下都进行如下顺序:源程序->词法分器->语法分析器->语义分析器->中间代码生成器->代码优化器->代码生成器->目标程序.
其中这中间6项活动都要涉及的两项主要活动是:符号管理器与错误处理器.
归根原因,这里有一个叫做符号表的东东在里面让你着魔一样不明白,其实符号表是一个数据结构.编译器的基本一项功能就是要记录源程序中使用的标识符并收集与每个标识符相关的各种属性信息.属性信息表明了该标识符的存储位置/类型/作用域(在那个阶段有效)等信息,通俗的说一下就是,当编译器看到一个符号声明时,例如你的函数名它就会把它放到这个符号表中去登记一下~符号表里存放着你的函数的入口地址,参数个数,返回信息等等一堆东西~而在联接阶段主要是处理工程中的符号表与调用对应处理关系,即我们通常所说的解引用.
 ==========================================================

修饰符extern

C语言中,修饰符extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。

 

1.     extern修饰变量的声明

举例来说,如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。这里需要注意的是,被引用的变量v的链接属性必须是外链接(external)的,也就是说a.c要引用到v,不只是取决于在a.c中声明extern int v,还取决于变量v本身是能够被引用到的。这涉及到c语言的另外一个话题--变量的作用域。能够被其他模块以extern修饰符引用到的变量通常是全局变量。还有很重要的一点是,extern int v可以放在a.c中的任何地方,比如你可以在a.c中的函数fun定义的开头处声明extern int v,然后就可以引用到变量v了,只不过这样只能在函数fun作用域中引用v罢了,这还是变量作用域的问题。对于这一点来说,很多人使用的时候都心存顾虑。好像extern声明只能用于文件作用域似的。

 

2.extern修饰函数声明

从本质上来讲,变量和函数没有区别。函数名是指向函数二进制块开头处的指针。如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。对其他模块中函数的引用,最常用的方法是包含这些函数声明的头文件。使用extern和包含头文件来引用函数有什么区别呢?extern的引用方式比包含头文件要简洁得多!extern的使用方法是直接了当的,想引用哪个函数就用extern声明哪个函数。这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间。在大型C程序编译过程中,这种差异是非常明显的。

 

3.extern修饰符可用于指示C或者C++函数的调用规范

比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。

 

//////////////////////////////////////////////////////////////////////////////////////////////////////

getchar函数的用法:

一、getchar的两点总结:
1.getchar是以行为单位进行存取的。

譬如下面程序段:

while((c= getchar()) != EOF)

{
    putchar(c);
}

执行程序,输入:abc,然后回车。则程序就会去执行puchar(c),然后输出abc,这个地方不要忘了,系统输出的还有一个回车。然后可以继续输入,再次遇到换行符的时候,程序又会把那一行的输入的字符输出在终端上


对于getchar,肯定很多初学的朋友会问,getchar不是以字符为单位读取的吗?那么,既然我输入了第一个字符a,肯定满足while循环(c = getchar()) != EOF的条件啊,那么应该执行putchar(c)在终端输出一个字符a。不错,我在用getchar的时候也是一直这么想的,但是程序就偏偏不着样执行,而是必需读到一个换行符或者文件结束符EOF才进行一次输出。

对这个问题的一个解释是,在大师编写C的时候,当时并没有所谓终端输入的概念,所有的输入实际上都是按照文件进行读取的,文件中一般都是以行为单位的。因此,只有遇到换行符,那么程序会认为输入结束,然后采取执行程序的其他部分。同时,输入是按照文件的方式存取的,那么要结束一个文件的输入就需用到EOF (Enf Of File). 这也就是为什么getchar结束输入退出时要用EOF的原因。

2.getchar()的返回值一般情况下是字符,但也可能是负值,即返回EOF

这里要强调的一点就是,getchar函数通常返回终端所输入的字符,这些字符系统中对应的ASCII值都是非负的。因此,很多时候,我们会写这样的两行代码:
char c;
c = getchar();

这样就很有可能出现问题。因为getchar函数除了返回终端输入的字符外,在遇到Ctrl+D(Linux)即文件结束符EOF时,getchar ()的返回EOF,这个EOF在函数库里一般定义为-1。因此,在这种情况下,getchar函数返回一个负值,把一个负值赋给一个char型的变量是不正确的。为了能够让所定义的变量能够包含getchar函数返回的所有可能的值,正确的定义方法如下(K&R C中特别提到了这个问题):
int c;
c = getchar();

二、EOF的两点总结(主要指普通终端中的EOF)
1.EOF作为文件结束符时的情况:

EOF虽然是文件结束符,但并不是在任何情况下输入Ctrl+D(Windows下Ctrl+Z)都能够实现文件结束的功能,只有在下列的条件下,才作为文件结束符。
(1):遇到getcahr函数执行时,要输入第一个字符时就直接输入Ctrl+D,就可以跳出getchar(),去执行程序的其他部分;
(2):在前面输入的字符为换行符时,接着输入Ctrl+D
(3):在前面有字符输入且不为换行符时,要连着输入两次Ctrl+D,这时第二次输入的Ctrl+D起到文件结束符的功能,至于第一次的Ctrl+D的作用将在下面介绍
其实,这三种情况都可以总结为只有在getchar()提示新的一次输入时,直接输入Ctrl+D才相当于文件结束符。

2.EOF作为行结束符时的情况,这时候输入Ctrl+D并不能结束getchar(),而只能引发getchar()提示下一轮的输入。

这种情况主要是在进行getchar()新的一行输入时,当输入了若干字符(不能包含换行符)之后,直接输入Ctrl+D,此时的Ctrl+D并不是文件结束符,而只是相当于换行符的功能,即结束当前的输入。

以上面的代码段为例,如果执行时输入abc,然后Ctrl+D,程序输出结果为:
abcabc

注意:第一组abc为从终端输入的,然后输入Ctrl+D,就输出第二组abc,同时光标停在第二组字符的c后面,然后可以进行新一次的输入。这时如果再次输入Ctrl+D,则起到了文件结束符的作用,结束getchar()。
如果输入abc之后,然后回车,输入换行符的话,则终端显示为:
abc //第一行,带回车
abc //第二行
//第三行

其中第一行为终端输入,第二行为终端输出,光标停在了第三行处,等待新一次的终端输入。
从这里也可以看出Ctrl+D和换行符分别作为行结束符时,输出的不同结果。
EOF的作用也可以总结为:当终端有字符输入时,Ctrl+D产生的EOF相当于结束本行的输入,将引起getchar()新一轮的输入;当终端没有字符输入或者可以说当getchar()读取新的一次输入时,输入Ctrl+D,此时产生的EOF相当于文件结束符,程序将结束getchar()的执行。

/////////////////////////////////////////////////////////////////////

波特率:9600bps

就是每秒中传输9600bit,也就是相当于每一秒中划分成了9600等份。

在电脑里,也就是超级终端等的端口设置。电脑的默认端口设置,也就是默认帧格式是:8个数据bit1个停止bit,(起始1bit是必须的)默认无奇偶,无流控。

则实际就是10bit1帧。一秒中可以发送9600/10=960个帧,也就是960字符,因为一帧里只有1个字符,1字符就是帧里面的8个数据bit

 

 

原创粉丝点击