c语言编程基础------0.0c语言介绍

注明:这篇文章主要内容来源与维基百科

http://zh.wikipedia.org/wiki/C%E8%AF%AD%E8%A8%80#C99



C语言

C語言
Cimage.jpg
编程范型程序式指令式编程过程式)、结构化编程
发行时间1972年
設計者丹尼斯·里奇(Dennis Ritchie)
實作者丹尼斯·里奇(Dennis Ritchie)和肯·汤普逊(Ken Thompson)
最新发行时间ISO/IEC 9899 2011C11) / 2011年11月
主要實作產品ClangGCCMSVCTurbo CWatcom C
啟發語言BBCPLCPL)、ALGOL 68[1]組合語言PL/IFORTRAN
影響語言大量
如:awkBitCcshC++C#
DJavaJavaScriptObjective-CPerlPHP
作業系統跨平台

C是一种通用的程式語言,广泛用于系统软件应用软件的开发。于1969年至1973年間,為了移植與開發UNIX作業系統,由丹尼斯·里奇肯·汤普逊,以B语言为基础,在贝尔实验室設計、开发出來。

C语言具有高效、灵活、功能丰富、表达力强和較高的可移植性等特点,在程序员中备受青睐,成为最近25年使用最为广泛的编程语言[2]。目前,C语言編譯器普遍存在於各種不同的操作系统中,例如Microsoft Windows, Mac OS X, Linux, Unix等。C語言的設計影響了众多後來的程式語言,例如C++Objective-CJavaC#等。

二十世纪八十年代,為了避免各開發廠商用的C語言語法產生差異,由美國國家標準局為C語言訂定了一套完整的國際標準語法,稱為ANSI C,作為C語言的標準。二十世纪八十年代至今的有关程式開發工具,一般都支持符合ANSI C的語法。

設計哲學

C語言設計目標是提供一種能以簡易的方式編譯、處理低階記憶體、產生少量的機械碼以及不需要任何執行環境支援便能執行的程式語言。C語言也很適合搭配汇编语言來使用。儘管C語言提供了許多低階處理的功能,但仍然保持著良好跨平台的特性,以一個標準規格寫出的C語言程式可在許多電腦平台上進行編譯,甚至包含一些嵌入式处理器(微控制器或称MCU)以及超級電腦等作業平台。

特色

  • C語言是一個有結構化程式設計、具有变量作用域(variable scope)以及遞迴功能的程序式語言。
  • C語言傳遞參數均是以值傳遞(pass by value)[3],另外也可以傳遞指针(a pointer passed by value)。
  • 不同的變數類型可以用結構体(struct)組合在一起。
  • 只有32個保留字(reserved keywords),使变量、函數命名有更多彈性。
  • 部份的变量類型可以轉換,例如整型和字符型变量。
  • 透過指针(pointer),C語言可以容易的對記憶體進行低階控制。
  • 编译预处理(preprocessor)讓C語言的編譯更具有彈性。

歷史

早期發展

肯·汤普逊和丹尼斯·里奇, 是C程式語言的开发者.

C語言最早由丹尼斯·里奇(Dennis Ritchie)為了在PDP-11電腦上運行的Unix系統所設計出來的程式語言,第一次發展在1969年到1973年之間。

C源于BCPL语言,后者由馬丁·理察德(Martin Richards)于1967年左右设计实现。BCPL是一门"无类型"的编程语言:它仅能操作一种数据类型,即机器字(machine word)。1970年,肯·汤普逊为运行在PDP-7上的首个Unix系统设计了一个精简版的BCPL,这个语言被称为B语言,它也是无类型的。

Unix最早運行在PDP-7上,是以組合語言寫成。在PDP-11出现後,丹尼斯·里奇與肯·汤普逊着手將Unix移植到PDP-11上,无类型的语言在PDP-11上愈发显得合适。PDP-11提供了多种不同规格大小的基本对象:一字节长的字符,两字节长的整型数以及四字节长的浮点数。B语言无法处理这些不同规格大小的对象,也没有提供单独的操作符去操作它们。

C语言最初尝试通过向B语言中增加数据类型的想法来处理那些不同类型的数据。和大多数语言一样,在C中,每个对象都有一个类型以及一个值;类型决定了可用于值的操作的含义,以及对象占用的存储空间大小。

1973年,Unix作業系統的核心正式用C語言改寫,這是C語言第一次應用在作業系統的核心編寫上。

1975年C语言开始移植到其他机器上使用。史蒂芬·強生实现了一套「可移植编译器」,这套编译器修改起来相对容易,并且可以为不同的机器生成代码。从那时起,C在大多数计算机上被使用,从最小的微型计算机到与CRAY-2超级计算机。C语言很规范,即使没有一份正式的标准,你也可以写出C程序,这些程序无须修改就可以运行在任何支持C语言和最小运行时环境的计算机上。

C最初在小型机器上实现,并且继承了一系列小语种编程语言的特点;与功能相比,C的设计者更倾向于简单和优雅。此外,从一开始,C语言就是为系统级编程而设计,程序的运行效率至关重要,因此,C语言与真实机器能力的良好匹配也就不足为奇。例如,C语言为典型硬件所直接支持的对象:字符整数(也许有多种大小),以及浮点数(同样可能有多种大小)提供了相应的基本数据类型。

K&R C

1978年,丹尼斯·里奇布萊恩·柯林漢合作出版了《C程序设计语言》的第一版。書中介紹的C語言標準也被C語言程式設計師稱作“K&R C”,第二版的書中也包含了一些ANSI C的標準。K&R C主要介绍了以下特色:

  • 结构(struct)类型
  • 长整数(long int)类型
  • 无符号整数(unsigned int)类型
  • 把运算符=+=-改为+=-=。因为=+=-會使得編譯器不知道使用者要處理i = -10還是i =- 10,使得處理上產生混淆。

即使在後來ANSI C標準被提出的許多年後,K&R C仍然是許多編譯器的最低標準要求,許多老舊的編譯仍然運行K&R C的標準。

ANSI C 和 ISO C

1989年,C语言被美國國家標準協會(ANSI)标准化,編號為ANSI X3.159-1989。這個版本又稱為C89。标准化的一个目的是扩展K&R C,增加了一些新特性。

  • void 函数
  • 函数返回 structunion 类型
  • void * 数据类型

1990年,国际标准化组织(ISO)成立 ISO/IEC JTC1/SC22/WG14 工作组,来规定国际标准的C语言,通过对ANSI标准的少量修改,最终製定了 ISO 9899:1990,又稱為C90。随后,ANSI亦接受國際標準C,並不再發展新的C標準。

K&R C语言到ANSI/ISO标准C语言的改进包括:

  • 增加了真正的标准库
  • 新的预处理命令与特性
  • 函数原型允许在函数申明中指定参数类型
  • 一些新的关键字,包括 constvolatilesigned
  • 宽字符、宽字符串与多字节字符
  • 对约定规则、声明和类型检查的许多小改动与澄清

WG14工作小组之後又於1994年,对1985年颁布的标准做了两处技术修订(缺陷修复)和一个补充(扩展)。下面是 1994 年做出的所有修改:

  • 3 个新的标准库头文件 iso646.h、wctype.h 和 wchar.h
  • 几个新的记号与预定义宏,用于对国际化提供更好的支持
  • printf/sprintf 函数一系列新的格式代码
  • 大量的函数和一些类型常量,用于多字节字符宽字节字符

C99

在ANSI的标准确立後,C语言的规范在一段时间内没有大的变动,然而C++在自己的标准化建立过程中继续发展壮大。《标准修正案一》在1994年为C语言建立了一个新标准,但是只修正了一些C89标准中的细节和增加更多更广的国际字符集支持。不过,这个标准引出了1999年ISO 9899:1999的发表。它通常被称为C99。C99被ANSI于2000年3月采用。

在C99中包括的特性有:

  • 增加了对编译器的限制,比如源程序每行要求至少支持到 4095 字节,变量名函数名的要求支持到 63 字节(extern 要求支持到 31)。
  • 增强了预处理功能。例如:
    • 巨集支持取可变参数 #define Macro(...) __VA_ARGS__
    • 使用巨集的时候,允许省略参数,被省略的参数会被扩展成空串。
    • 支持 // 开头的单行注释(这个特性实际上在C89的很多编译器上已经被支持了)
  • 增加了新关键字 restrict, inline, _Complex, _Imaginary, _Bool
    • 支持 long long, long double _Complex, float _Complex 等类型
  • 支持不定长的数组,即数组长度可以在运行时决定,比如利用变量作为数组长度。声明时使用 int a[var] 的形式。不过考虑到效率和实现,不定长数组不能用在全局,或 structunion 里。
  • 变量声明不必放在语句块的开头,for 语句提倡写成 for(int i=0;i<100;++i) 的形式,即i 只在 for 语句块内部有效。
  • 允许采用(type_name){xx,xx,xx} 类似于 C++ 的构造函数的形式构造匿名的结构体。
  • 初始化结构的时候允许对特定的元素赋值,形式为:
    struct test { int a [ 3 ],b ; } foo [ ] =   { [ 0 ]. a = { 1 } , [ 1 ]. a = 2 } ;
    struct test { int a , b , c , d ; } foo =   { . a = 1 , . c = 3 , 4 , . b = 5 } ;   // 3,4 是对 .c,.d 赋值的
  • 格式化字符串中,利用 \u 支持 unicode 的字符。
  • 支持 16 进制的浮点数的描述。
  • printf scanf 的格式化串增加了对 long long int 类型的支持。
  • 浮点数的内部数据描述支持了新标准,可以使用 #pragma 编译器指令指定。
  • 除了已有的 __line__ __file__ 以外,增加了 __func__ 得到当前的函数名。
  • 允许编译器化简非常数的表达式。
  • 修改了 / % 处理负数时的定义,这样可以给出明确的结果,例如在C89中-22 / 7 = -3, -22 % 7 = -1,也可以-22 / 7= 4, -22 % 7 = 6。 而C99中明确为 -22 / 7 = -3, -22 % 7 = -1,只有一种结果。
  • 取消了函数返回类型默认为 int 的规定。
  • 允许 struct 定义的最后一个数组不指定其长度,写做 [](flexible array member)。
  • const const int i 将被当作 const int i 处理。
  • 增加和修改了一些标准头文件,比如定义 bool 的 <stdbool.h> ,定义一些标准长度的 int 的 <inttypes.h> ,定义复数的 <complex.h> ,定义宽字符的 <wctype.h> ,类似于泛型的数学函数 <tgmath.h>, 浮点数相关的 <fenv.h>。 在<stdarg.h> 增加了 va_copy 用于复制 ... 的参数。 里增加了 struct tmx ,对 struct tm 做了扩展。
  • 输入输出对宽字符以及长整数等做了相应的支持。

但是各个公司对C99的支持所表现出来的兴趣不同。当GCC和其它一些商业编译器支持C99的大部分特性的时候[4]微软Borland却似乎对此不感兴趣。

C11

2011年12月8日,ISO正式发布了新的C语言的新标准C11,之前被称为C1X,官方名称为ISO/IEC 9899:2011

新的标准提高了对C++的兼容性,并增加了一些新的特性。这些新特性包括:

  • 对齐处理(Alignment)的标准化(包括_Alignas标志符,alignof运算符, aligned_alloc函数以及<stdalign.h>头文件
  • _Noreturn 函数标记,类似于 gcc 的 __attribute__((noreturn))。
  • _Generic 关键字。
  • 多线程(Multithreading)支持,包括:
    • _Thread_local存储类型标识符,<threads.h>头文件,里面包含了线程的创建和管理函数。
    • _Atomic类型修饰符和<stdatomic.h>头文件。
  • 增强的Unicode的支持。基于C Unicode技术报告ISO/IEC TR 19769:2004,增强了对Unicode的支持。包括为UTF-16/UTF-32编码增加了char16_t和char32_t数据类型,提供了包含unicode字符串转换函数的头文件<uchar.h>.
  • 删除了 gets() 函数,使用一个新的更安全的函数gets_s()替代。
  • 增加了边界检查函数接口,定义了新的安全的函数,例如 fopen_s(),strcat_s() 等等。[5]
  • 增加了更多浮点处理宏。
  • 匿名结构体/联合体支持。这个在gcc早已存在,C11将其引入标准。
  • 静态断言(Static assertions),_Static_assert(),在解释 #if 和 #error 之后被处理。
  • 新的 fopen() 模式,(“…x”)。类似 POSIX 中的 O_CREAT|O_EXCL,在文件锁中比较常用。
  • 新增 quick_exit() 函数作为第三种终止程序的方式。当 exit()失败时可以做最少的清理工作。

語法

Hello World 程序

下面是一个在标准输出设备 (stdout) 上,印出 "hello, world" 字符串的简单程序。类似的程序,通常作为初學编程语言时的第一个程序:

# include <stdio.h>
int main ( void )
  {
  printf ( "hello, world\n" ) ;
  return 0 ;
  }

进一步了解

C语言由函数和变量组成,C的函数就像是Fortran中的子程序和函数。

在C语言中,程序从 main 开始执行。main 函数通过调用和控制其他函数进行工作。例如上面的printf。程序员可以自己写函数,或从库中调用函数。在上面的return 0; 使得 main 返回一个值给调用程序的壳层,表明程序是否成功运行。

一个C语言的函数由返回值、函数名、参数列表和函数体组成。函数体的语法和其它的复合的语句部分是一样的。

复合语句

C语言中的复合语句(或称语句块)的格式为:

{
    语句 ;
    语句 ;
    /* ... */
}

复合语句可以使得几个语句从文法上变成一个语句。

有时必须使用复合语句,否则会产生错误。例如,在运用循环语句的时候,如果循环体(即循环中执行部分)包含多个语句(以分号隔开),则必须使用花括号将他们合并成一个复合语句。如果不这么做,系统仅把第一个分号前的内容看做循环体。

需要注意的是,部分C编译器并不支持在任意位置使用复合语句。

條件語句

C語言有三種條件語句形式。兩種是if,另一種是switch

兩種if包括:

if (運算式 )
    語句 ;

以及

if (運算式 )
    語句 ;
else
    語句 ;

運算式的值非零表示條件為真;如果條件为假,程式將跳過if处的語句,直接執行if後面的語句。但是如果if後面有else,則當條件为假時,程式跳到else處執行。ifelse後面的語句可以是另個if語句,這種套疊式的結構,允許更複雜的邏輯控制流程得以實現。在一般情況下,else一定與最接近的if成對,必要時可用括弧{}越過此限制。比較下面兩種情況:

if (逻辑表达式 )
    if (逻辑表达式 )
        語句 ;
    else
        語句 ;
if (逻辑表达式 )
{
    if (逻辑表达式 )
        語句 ;
}
else
    語句 ;

switch通常用於對幾種有明確值的條件進行控制。它要求的條件值通常是整數或字元。與switch搭配的條件轉移是case。使用case後面的標值,控制程式將跳到滿足條件的case處一直往下執行,直到語句結束或遇到break。通常可以使用default把其他例外的情況包含進去。如果switch語句中的條件不成立,控制程式將跳到default處執行;如果省略default子句,則直接執行下一語句。switch是可以嵌套的。

switch (逻辑表达式 )
{
    case1 :
        語句段 1 ;
        break ;
    case2 :
        語句段 2 ;
        break ;
    default :
        語句段 3 ;
}

循環語句(迴圈)

C語言有三種形式的循環語句:

do
    語句
while (表達式 ) ;

while (表達式 )
    語句 ;

for (表達式 1 ; 表達式 2 ; 表達式 3 )
    語句 ;

whiledo中,語句將執行到表達式的值為零時结束。在do...while語句中,循環將至少被執行一次。这三種循環結構可以互相轉化:

for ( <表達式 1 >; <表達式 2 >; <表達式 3 > )
      <語句 >;

如果<語句>中不使用continue語句的话,相當於

<表達式 1 >;
while ( <表達式 2 > ) {
    <語句 >;
    <表達式 3 >;
}

當循環條件一直為真時,將產生無窮迴圈。

跳轉語句

跳轉語句包括四種:goto,continue,break和return

goto語句是無條件轉移語句:

goto 標記;

標記必須在當前函數中定義,使用“標記:”的格式定義。程式將跳到標記處繼續執行。由於goto容易產生閱讀上的困難,所以應該儘量少用。

continue語句用在迴圈語句中,作用是結束當前一輪的迴圈,馬上開始下一輪迴圈。

break語句用在迴圈語句或switch中,作用是結束當前迴圈,跳到循環體外繼續執行。但是使用break只能跳出一層迴圈。在要跳出多重迴圈時,可以使用goto使得程式更為簡潔。

當一個函數執行結束後要返回一個值時,使用returnreturn可以跟一個運算式或變數。如果return後面沒有值,將執行不返回值。

在C語言中的运算符号

()、 []、 -> 、 .、 !、 ++、 --  圆括号、方括号、指標、成員、逻辑非、自加、自减
++ 、 -- 、 * 、 & 、 ~ 、 ! 、 + 、 - 、 sizeof、(cast) 单目运算符
* 、 / 、 %算术运算符
+ 、 - 算术运算符
<< 、 >>位运算符
< 、 <= 、 > 、 >=关系运算符
== 、 !=关系运算符号
&位与
^位异或
|位或
&&逻辑与
||逻辑或
 ? 、 :条件运算符
= 、 += 、 -= 、 *= 、 /= 、 %= 、 &= 、 |= 、 ^=赋值运算符
,顺序运算符

比較特別的是,位元右移(>>)運算子可以是算術(左端補最高有效位)或是邏輯(左端補 0)位移。例如,將 11100011 右移 3 位元,算術右移後成為 11111100,邏輯右移則為 00011100。因算術位元右移較適於處理帶負號整數,所以幾乎所有的編譯器都是算術位元右移。[6]

运算符的优先级从高到低大致是:单目运算符、算术运算符、关系运算符、逻辑运算符、条件运算符、赋值运算符(=)和逗号运算符。

数据类型

基础数据类型

注意:以下是典型的数据位长和范围。编译器可能使用不同的数据位长和范围。请参考具体的参考手册。

在头文件<limits.h>和<float.h>中说明了基础数据的长度。float,double和long double的范围就是在IEEE 754标准中提及的典型数据。

关键字位长(字节)范围格式化字符串
char1-128..127(或0..255,与体系结构相关) %c
unsigned char10..255 %c, %hhu
signed char1-128..127 %c, %hhd, %hhi
int2(16位系统) 或
4
-32768..32767 或
-2147483648..2147483647
 %i, %d
unsigned int2 或
4
0..65535 或
0..4294967295
 %u
signed int2 或
4
-32768..32767 或
-2147483648..2147483647
 %i, %d
short int2-32768..32767 %hi, %hd
unsigned short20..65535 %hu
signed short2-32768..32767 %hi, %hd
long int4 或
8[7]
-2147483648..2147483647 或
-9223372036854775808..9223372036854775807
 %li, %ld
unsigned long4 或
8
0..4294967295 或
0..18446744073709551615
 %lu
signed long4 或
8
-2147483648..2147483647 或
-9223372036854775808..9223372036854775807
 %li, %ld
long long8-9223372036854775808..9223372036854775807 %lli, %lld
unsigned long long80..18446744073709551615 %llu
float43.4x10−38..3.4x10+38 (7 sf) %f, %e, %g
double81.7x10−308..1.7x10+308 (15 sf) %lf, %e, %g
long double8 或以上編譯器相關 %Lf, %Le, %Lg
结构数据类型

结构数据类型允许构造由多个基础数据类型组合而成的复杂结构,结构数据类型为面向对象的蓝本。以下的结构数据类型通过指针实现了二叉树结构:

struct binarytree {
  int data ;
  struct binarytree *lchild ; // left child of the node
  struct binarytree *rchild ; // right child of the node
}

为结构数据类型定义变量时通常会用到动态内存分配:

struct binarytree *tree ;
tree = ( struct binarytree * ) malloc ( sizeof ( struct binarytree ) ) ; // 分配该结构所需的内存单元数量到tree指针
tree ->data = 1 ;
tree ->lchild = ( struct binarytree * ) malloc ( sizeof ( struct binarytree ) ) ;
...

由于C语言不具备自动垃圾收集(Garbage Collection)功能,使用完毕后调用free()来释放之前通过malloc()分配的内存。详见以下指针章节。

陣列

如果一個變數名後面跟著一個有數字的中括弧,這個聲明就是陣列聲明。字串也是一種陣列,它們以ASCII的NUL作為陣列的結束。要特別注意的是,方括內的索引值是從0算起的。

例如:

int myvector [ 100 ]/* 從myvector[0]至myvector[99]止共100個元素 */
char mystring [ 80 ]
float mymatrix [ 3 ] [ 2 ] = { 2.0 , 10.0 , 20.0 , 123.0 , 1.0 , 1.0 } ;
int notfull [ 3 ] [ 3 ] = { { 1 } , { 1 , 2 , 3 } , { 4 , 5 } } ;               ( * )
char lexicon   [ 10000 ] [ 300 ]/* 共一萬個最大長度為300的字元陣列。*/
int a [ 3 ] [ 4 ]

上面最後一個例子創建了一個陣列,但也可以把它看成是一個多維陣列。注意陣列的下標從0開始。這個陣列的結構如下:

a[0][0]a[0][1]a[0][2]a[0][3]
a[1][0]a[1][1]a[1][2]a[1][3]
a[2][0]a[2][1]a[2][2]a[2][3]

例子中notfull創建了一個3*3的二維陣列,初始化時有些元素並未賦值。如下:

1 0 0
1 2 3
4 5 0

為0的位置的數值是隨機的,但通常會被補上0。

指针

如果一个变量声明时在前面使用 * 号,表明这是个指针型变量。换句话说,该变量存储一个地址,而 *(此处特指单目运算符 * ,下同。C语言中另有 双目运算符 * ) 则是取内容操作符,意思是取这个内存地址里存储的内容。指针是 C 语言区别于其他同时代高级语言的主要特征之一。

指针不仅可以是变量的地址,还可以是数组、数组元素、函数的地址。通过指针作为形式参数可以在函数的调用过程得到一个以上的返回值(不同于return(z)这样的仅能得到一个返回值。

指针是一把双刃剑,许多操作可以通过指针自然的表达,但是不正确的或者过分的使用指针又会给程序带来大量潜在的错误。

例如:

int * pi ;     /* 指向整型数据的指针变量 */
int * api [ 3 ] ; /* 由指向整型数据的指针构成的数组,长度为 3 */
char ** argv ; /* 指向一个字符指针的指针 */
struct
{
    int member ;
} stinst , * pst = & stinst ;       /* pst是一个指向一个匿名结构体的指针 */

储存在指针中的地址所指向的数值在程序中可以由 * 读取。例如,在第一个例子中, *pi 是一个整型数据。这叫做引用一个指针。

另一个运算符 &,叫做取地址运算符,它将返回一个变量、数组或函数的存储地址。因此,下面的例子:

int i , *pi ; /* int and pointer to int */
pi = &i ;

i*pi 在程序中可以相互替换使用,直到 pi 被改变成指向另一个变量的地址。

当指针指向结构体时,可以使用运算符 -> 代替 *和. 的作用,如以下两个表达式为等效:

( *pst ). member
pst ->member
字串

C语言的字符串其实就是char型数组,所以使用字符串並不需要引用庫。然而C標準庫確實包含了用於對字符串進行操作的函數,使得它們看起來就像字符串而不是陣列。使用這些函數需要引用標頭檔<string.h>

文件輸入/輸出

在C語言中,輸入和輸出是經由標準函式庫中的一組函數來實現的。在ANSI/ISO C中,這些函數被定義在標頭檔<stdio.h>中。

標準輸入/輸出

有三個標準輸入/輸出是标准I/O库預先定義的:

  • stdin 標準輸入
  • stdout 標準輸出
  • stderr 輸入輸出錯誤

下面的這個例子顯示了一個過濾程式(filter program)是怎樣構成的。

# include <stdio.h>

int main ( int argc , const char * argv [ ] )
{
    int c ;
    while ( 1 )
    {
        c = getchar ( ) ;
        if (c ==EOF )
        {
            perror ( "getchar()" ) ;
            return - 1 ;
        }
        putchar (c ) ;
    }

    return 0 ;
}

函数

C语言的基本结构单位是函数。系统首先调用 main函数(主函数),通过函数的嵌套调用,再调用其他函数。函数可以是系统自带的函数,也可以是用户定义的函数。C语言中,不允许函数嵌套定义。

内存管理

C语言的特色之一是:程序员必须亲自处理内存的分配细节。

C语言使用栈(Stack)来保存函数返回地址/栈祯基址、完成函数的参数传递和函数局部变量的存储。 如果程序需要在运行的过程中动态分配内存,可以利用(Heap)来实现。

基本上C程序的元素存储在内存的时候有3种分配策略:

  • 静态分配

如果一个变量声明为全局变量或者是函数的静态变量,这个变量的存储将使用静态分配方式。静态分配的内存一般会被编译器放在数据段代码段来存储,具体取决于实现。这样做的前提是,在编译时就必须确定变量的大小。 以IA32的x86平台及gcc编译器为例,全局及静态变量放在数据段的低端;全局及静态常量放在代码段的高端。

  • 自动分配

函数的自动局部变量应该随着函数的返回会自动释放(失效),这个要求在一般的体系中都是利用栈(Stack)来满足的。相比于静态分配,这时候,就不必绝对要求这个变量在编译时就必须确定变量的大小,运行时才决定也不迟,但是C89仍然要求在编译时就要确定,而C99放松了这个限制。但无论是C89还是C99,都不允许一个已经分配的自动变量运行时改变大小。

所以说C函数永远不应该返回一个局部变量的地址

要指出的是,自动分配也属于动态分配,甚至可以用alloca函数来像分配堆(Heap)一样进行分配,而且释放是自动的。

  • 动态分配

还有一种更加特殊的情况,变量的大小在运行时有可能改变,或者虽然单个变量大小不变,变量的数目却有很大弹性,不能静态分配或者自动分配,这时候可以使用(Heap)来满足要求。ANSI C定义的堆操作函数是malloc、calloc、realloc和free。

使用(Heap)内存将带来额外的开销和风险。

安全问题

C语言的特色之一是:语言不负责内存边界检查。

C語言的标准文档要求了一个平台移植C语言的时候至少要实现的一些功能和封装的集合,称为“标准库”,标准庫的声明头部通過预处理器命令#include進行引用。

在C89標準中:

文件简介说明
<assert.h>断言相关
<ctype.h>字符类型判断
<errno.h>标准报错机制
<float.h>浮点运算
<limits.h>各种体系结构限制
<locale.h>本地化接口
<math.h>数学函数
<setjmp.h>跨函数跳转
<signal.h>信号(类似UNIX信号定义,但是差很远)
<stdarg.h>可变参处理
<stddef.h>一些标准宏定义
<stdio.h>标准I/O库
<stdlib.h>标准工具库函数
<string.h>ASCIIZ字符串及任意内存处理函数
<time.h>时间相关

在94年的修正版中

  • <iso646.h>
  • <wchar.h>
  • <wctype.h>

在C99中增加了六個函式庫

  • <complex.h>
  • <fenv.h>
  • <inttypes.h>
  • <stdbool.h>
  • <stdint.h>
  • <tgmath.h>

以上是C语言的标准。各个系统各自又对C库函数进行的各种扩充,就浩如烟海了。如POSIX CGNU C等。

工具軟體

工具軟體可以幫助程式設計者避免一些程式中潛藏或容易出現的問題,例如常會造成程式未預期動作或是執行期錯誤的程式碼。

許多語言都有自動源代碼檢查及審計工具,C語言也有類似工具,像是Lint。可以在程式剛寫好時用Lint找出可能有問題的程式,通過Lint後再用C編譯器進行編譯,許多編譯器也可以設定是否要針對一些可能有問題的程式碼提出警告。MISRA C是一套針對嵌入式系統的法則,可主要也是避免一些可能有問題的程式碼。

也有一些編譯器、程式庫或作業系統可以處理一些非標準C語言的功能,例如邊界值檢查、缓存溢出偵測、序列化自動垃圾回收功能。

使用像ValgrindIBM Rational Purify等軟體工具,或者連結有特別malloc函式的程式庫,有助於找出一些運行期記憶體使用的問題。

保留关键字

charshortintunsigned
longfloatdoublestruct
unionvoidenumsigned
constvolatiletypedefauto
registerstaticexternbreak
casecontinuedefaultdo
elseforgotoif
returnswitchwhilesizeof

C99新增关键字

_Bool_Complex_Imaginaryinlinerestrict

C11新增关键字

_Alignas_Alignof_Atomic_Generic_Noreturn
_Static_assert_Thread_local

[8]

經典錯誤

void main的用法并不是任何標准制定的[9][10]。 C語言标准語法是int main,任何实现都必须支持int main(void) { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[11]。 在 C++ 標準中,main的標準型態應是 int,否则类型是由实现定义的。任何实现都必须支持int main() { /* ... */ }int main(int argc, char* argv[]) { /* ... */ }[12]

参见

文獻例證

  1. ^ Ritchie, Dennis M.. The Development of the C Language. January 1993 [1 January 2008]. "The scheme of type composition adopted by C owes considerable debt to Algol 68, although it did not, perhaps, emerge in a form that Algol's adherents would approve of."
  2. ^ TIOBE Programming Community Index [TIOBE编程社区指数]. 2012 [2012-11-03] (英文).
  3. ^ Brian W. Kernighan and Dennis M. Ritchie. The C programming Language. Prentice-Hall. 1988. ISBN 0-13-110362-8 (英文). "In C, all function arguments are passed ``by value."
  4. ^ GCC从3.0版本开始正式支持C99(-std=c99)
  5. ^ WG14 N1570 Committee Draft — April 12, 2011 Annex K Bounds-checking interfaces
  6. ^ 经测试,gcc4.4.3使用C99标准的结果依然是00011100,即逻辑移位。
  7. ^ GCC、Clang 等实现中,64位代码的long类型为64位,而MSVC中则维持32位
  8. ^ ISO/IEC 9899:201x (ISO C11) Committee Draft
  9. ^ Can I write "void main()"?The definition void main() { /* ... */ } is not and never has been C++, nor has it even been C.
  10. ^ 用 C99 进行开放源代码的开发
  11. ^ 「The function called at program startup is named main. The implementation declares no prototype for this function. It shall be defined with a return type of int and with no parameters: int main(void) { /* ... */ } or with two parameters (referred to here as argc and argv, though any names may be used, as they are local to the function in which they are declared): int main(int argc, char *argv[]) { /* ... */ } or equivalent; or in some other implementation-defined manner.」,引自ISO/IEC 9899:1999, Section 5.1.2.2.1 Program startup
  12. ^ 「An implementation shall not predefine the main function. This function shall not be overloaded. It shall have a return type of type int, but otherwise its type is implementation-defined. All implementations shall allow both of the following definitions of main: int main() { /* ... */ } and int main(int argc, char* argv[]) { /* ... */ }.」,引自 ISO/IEC 14882, 第一版(1998)、第二版(2003)與第三版(2011), section 3.6.1 Main function

參考文獻

外部連結


  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
爬绳机器人的C语言编程需要先确定硬件平台,比如使用哪种单片机、机器人控制板、电机驱动器等。然后,需要根据机器人的运动方式(比如步态)和传感器输入(比如加速度计、陀螺仪)来设计算法。 以下是一个简单的爬绳机器人C语言编程的例子,仅供参考: ```c #include <stdio.h> #include <stdlib.h> #include <math.h> #include <stdbool.h> #include <string.h> // 定义舵机的最小、最大角度 #define SERVO_MIN_ANGLE 0 #define SERVO_MAX_ANGLE 180 // 定义电机的最大速度 #define MOTOR_MAX_SPEED 100 // 定义加速度计、陀螺仪的灵敏度 #define ACCELEROMETER_SENSITIVITY 8192.0 #define GYROSCOPE_SENSITIVITY 65.536 // 定义机器人的重心高度、臂长等参数 #define ROBOT_HEIGHT 0.3 #define ARM_LENGTH 0.15 // 定义控制循环的时间间隔 #define CONTROL_INTERVAL 0.02 // 定义机器人的运动状态 typedef enum { STATE_IDLE, STATE_CLIMBING, STATE_FALLING, } RobotState; // 定义电机控制结构体 typedef struct { int speed; bool direction; } MotorControl; // 定义舵机控制结构体 typedef struct { int angle; } ServoControl; // 定义加速度计、陀螺仪结构体 typedef struct { float x; float y; float z; } AccelerometerData, GyroscopeData; // 定义机器人状态结构体 typedef struct { RobotState state; float pitch; float roll; float yaw; float height; float arm_angle; MotorControl motor; ServoControl servo; AccelerometerData accelerometer; GyroscopeData gyroscope; } RobotStateData; // 初始化机器人状态结构体 void initRobotStateData(RobotStateData* data) { data->state = STATE_IDLE; data->pitch = 0.0; data->roll = 0.0; data->yaw = 0.0; data->height = ROBOT_HEIGHT; data->arm_angle = 0.0; data->motor.speed = 0; data->motor.direction = true; data->servo.angle = 0; data->accelerometer.x = 0.0; data->accelerometer.y = 0.0; data->accelerometer.z = 0.0; data->gyroscope.x = 0.0; data->gyroscope.y = 0.0; data->gyroscope.z = 0.0; } // 读取加速度计数据 void readAccelerometerData(AccelerometerData* data) { // TODO: 读取加速度计数据 } // 读取陀螺仪数据 void readGyroscopeData(GyroscopeData* data) { // TODO: 读取陀螺仪数据 } // 控制电机速度 void controlMotorSpeed(MotorControl* control) { // TODO: 控制电机速度 } // 控制舵机角度 void controlServoAngle(ServoControl* control) { // TODO: 控制舵机角度 } // 计算机器人姿态 void calculateRobotOrientation(RobotStateData* data) { float ax = data->accelerometer.x / ACCELEROMETER_SENSITIVITY; float ay = data->accelerometer.y / ACCELEROMETER_SENSITIVITY; float az = data->accelerometer.z / ACCELEROMETER_SENSITIVITY; float gx = data->gyroscope.x / GYROSCOPE_SENSITIVITY; float gy = data->gyroscope.y / GYROSCOPE_SENSITIVITY; float gz = data->gyroscope.z / GYROSCOPE_SENSITIVITY; float pitch = atan2(ax, sqrt(ay * ay + az * az)) * 180.0 / M_PI; float roll = atan2(ay, sqrt(ax * ax + az * az)) * 180.0 / M_PI; float yaw = data->yaw + gz * CONTROL_INTERVAL; data->pitch = pitch; data->roll = roll; data->yaw = yaw; } // 计算机器人臂角度 void calculateRobotArmAngle(RobotStateData* data) { float pitch = data->pitch * M_PI / 180.0; float arm_angle = asin((data->height - ROBOT_HEIGHT) / sqrt(pitch * pitch + 1)) - pitch; data->arm_angle = arm_angle * 180.0 / M_PI; } // 控制机器人状态 void controlRobotState(RobotStateData* data) { switch (data->state) { case STATE_IDLE: // TODO: 控制机器人进入攀爬状态 break; case STATE_CLIMBING: // TODO: 控制机器人攀爬 break; case STATE_FALLING: // TODO: 控制机器人下降 break; } } int main() { RobotStateData data; initRobotStateData(&data); while (true) { readAccelerometerData(&data.accelerometer); readGyroscopeData(&data.gyroscope); calculateRobotOrientation(&data); calculateRobotArmAngle(&data); controlRobotState(&data); controlMotorSpeed(&data.motor); controlServoAngle(&data.servo); // 延时 delay(CONTROL_INTERVAL * 1000); } return 0; } ``` 这个例子实现了读取加速度计、陀螺仪数据,计算机器人姿态、臂角度,控制机器人进入攀爬状态、攀爬、下降,以及控制电机速度、舵机角度。需要注意的是,这个例子并不完整,需要根据实际需求进行修改和完善。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值