1. C语言程序设计

1.1 什么是C语言?

C语言是1972年由Dennis Ritchie在美国 AT&T 公司贝尔实验室开发的一种编程语言。

任何编程语言都可以分为两类。

  • 面向问题的语言(高级语言)
  • 面向机器的语言(低级语言)

但C语言被认为是一种中级语言。 C是模块化、可移植、可重复使用的。

1.2 C程序的特点

  • 结构化语言
    • 它具有划分和隐藏所有信息和指令的能力。
    • C语言中的代码可以用函数或代码块来划分。
    • 与其他语言相比,C语言是一种结构化的语言。
  • 通用语言
    • 使其成为系统编程的理想语言。
    • 它也可以用于商业和科学应用。
    • ANSI在1983年为c语言建立了一个标准。
    • c语言拥有操纵位、字节和地址的能力。
    • 它在1990年以后被采用。
  • 可移植性
    • 可移植性是指可以移植或使用编写的软件。
    • 一个计算机C语言程序可以重复使用。
    • 通过修改或不修改。
  • 代码的可重用性&定制和扩展能力
    • 一个程序员可以轻松地创建自己的函数。
    • 它可以在不同的应用中反复使用。
    • C程序基本上是函数的集合。
    • 函数由'c'库支持。
    • 函数可以持续添加到'c'库中。
  • 关键字数量有限
    • 在'C'中只有32个关键词。
    • 里奇给出了27个关键词。
    • 5个是由美国国家标准委员会添加的。
    • 'C'的优势在于它的内置函数。
    • Unix系统提供了尽可能多的C函数。
    • 有些函数是在操作中使用。
    • 其他的函数是用于专业的应用。

1.3 C程序结构

pre-processor directives
global declarations
main()
{
    local variable deceleration
    statement sequences
    function invoking
}

1.4 C关键词

关键词是指那些已经向C语言编译器解释过含义的词。 C语言中只有32个关键词,这些关键词也被称为"保留词"。

auto        double      int         struct
break       else        long        switch
case        enum        register    typedef
char        extern      return      union
const       float       short       unsigned
continue    for         signed      void
default     goto        sizeof      volatile
do          if          static      while

1.5 C字符集

字符表示任何用于表示信息的字母、数字或特殊符号。 以下是C语言中允许使用的有效字母、数字和特殊符号。

  • 字母 - A, B, ...., Y, Z a, b, ..., ..., y, z
  • 数字 - 0、1、2、3、4、5、6、7、8、9
  • 特殊符号 - ~ ' ! @ # % ^ & * ( ) _ - + = | { }[ ] : ; " ' < > , . ? /

1.6 C程序的编写、编译和执行规则

  • C是区分大小写的,这意味着名为 "COUNTER "的变量与名为 "counter "的变量不同。
  • 所有的关键字都是小写的。
  • 关键词不能用于其他任何目的(如变量名)。
  • 每个C语言的语句都必须以;结束。因此,;作为语句的终结者。
  • 第一个字符必须是字母或下划线,除下划线外,不允许使用特殊符号,在变量、常量或关键字中不允许使用逗号或空格。
  • 为了提高语句的可读性,可以在两个字之间插入空格。但是,在变量、常数或关键字中不允许有空格。
  • 在程序中使用变量之前,必须先声明变量。
  • 文件的扩展名应该是.c
  • 程序在执行前必须先编译。

1.7 数据类型和占位符

  • C语言有 5 种基本的内置数据类型。
  • 数据类型定义了一个变量可以存储的一组值以及可以对其进行的一系列操作。
  • 一个变量在不同的时间有不同的值。
  • 声明一个变量的一般形式是:
type name
  • 下面是一个使用变量的例子。
#include<stdio.h>
main()
{
    int sum;
    sum=12;
    sum=sum+5;
    printf("Sum is %d",sum);
}

printf 函数将打印以下内容:

Sum is 17

其实 %d 是整数变量值的 占位符变量名 在双引号之后。

常见的数据类型有

  • int - integer
  • char - character
  • long - long integer
  • float - float number
  • double - long float

其他占位符是:

Placeholders Format
%c Character
%d Signed decimal integer
%i Signed decimal integer
%e Scientific notation[e]
%E Scientific notation[E]
%f Decimal floating point
%o unsigned octal
%s String of character
%u unsigned decimal integer
%x unsigned Hexadecimal (lower)
%X unsigned Hexadecimal (upper)
%p dispaly a pointer
%% print a %

1.8 控制字符(转义序列)

某些非打印字符以及反斜杠()和撇号('),可以用转义序列来表示。

a - Bell n - New line r - Carriage return b - Backspace f - Formfeed(换页) t - 水平方向tab " - 引号 v - 竖直方向tab ' - Apostrophe \ - Backslash ? - 问号 0 - Null

1.9 从键盘接收输入值

scanf功能用于接收来自键盘的输入。 scanf函数的一般形式是

scanf("Format string",&variable,&variable,...);

Format string 包含了我们要从键盘上接收的变量的占位符。在变量列表中的每一个变量名称前都有一个 & 号。 字符串是这个规则的例外,它们不会在前面加上这个符号。

注意:除了占位符和一些特殊字符外,不允许在格式字符串中插入任何其他字符。即使输入一个空格或其他不需要的字符, 也会导致你的程序工作不正确,结果会出乎意料。因此,请确保你只在scanf格式字符串中插入占位符。

下面的例子是接收来自键盘的多个变量。

float a;
int n;
scanf("%d%f",&n,&a);

请注意,scanf函数没有内置错误检查功能。程序员负责验证输入数据(类型、范围等)并防止错误。

2. 表达式和运算符的优先级

下表总结了所有运算符的优先级和关联性规则,包括我们尚未讨论过的运算符。 同一行的运算符具有相同的优先级;行的优先级依次递减,

因此,例如,*、/、和% 都具有相同的优先级,高于二进制的+和-。 操作符 () 指的是函数调用。操作符 -> 和 . 用于访问结构中的成员。

DESCRIPTION            OPERATORS    ASSOCIATIVITY
Function Expression ()      Left to Right
Array Expression    []      Left to Right
Structure Operator  ->      Left to Right
Structure Operator  .       Left to Right
Unary minus -       Right to Left
Increment/Decrement ++, --  Right to Left
Ones compliment    ~       Right to Left
Negation    !       Right to Left
Address of  &       Right to Left
Value of address    `*`     Right to Left
Type cast   (type)  Right to Left
Size in bytes       sizeof  Right to Left
Multiplication      `*`     Left to Right
Division    /       Left to Right
Modulus     %       Left to Right
Addition    +       Left to Right
Subtraction -       Left to Right
Left shift  <<      Left to Right
Right shift >>      Left to Right
Less than   <       Left to Right
Less than or equal to       <=      Left to Right
Greater than        >       Left to Right
Greater than or equal to    >=      Left to Right
Equal to    ==      Left to Right
Not equal to        !=      Left to Right
Bitwise AND &       Left to Right
Bitwise exclusive OR        ^       Left to Right
Bitwise inclusive OR        |       Left to Right
Logical AND &&      Left to Right
Logical OR  ||      Left to Right
Conditional ?:      Right to Left
Assignment  =, *=, /=, %=, +=, -=, &=, ^=, |=, <<=, >>=     Right to Left
Comma       ,       Right to Left

一元运算符(Unary) &、+、-和 * 的优先级高于二元运算符(Binary)。

3. 决策控制结构

C有三个主要的决策指令--if语句、if-else 语句和 switch 语句。

3.1 if 语句

C使用关键字 if 来实现决策控制指令。if 语句的一般形式是这样的。

//for single statement
if(condition)
    statement;

//for multiple statement
if(condition)
{
    block of statement;
}

更一般的形式如下。

//for single statement
if(expression)
    statement;

//for multiple statement
if(expression)
{
    block of statement;
}

这里的表达式可以是任何有效的表达式,包括关系式。我们甚至可以在 if statement 中使用算术表达式。

例如,以下所有的if语句都是有效的

if (3 + 2 % 5)
    printf("This works");

表达式 (3 + 2 % 5) 的值为 5,由于 5 为非零,所以被认为是 True。 因此 printf("This works"); 被执行。

3.2 if-else 语句

当 if 后面的表达式求值为真时,if 语句就会执行一个语句,或者一组语句。 当表达式评价为false时,它什么也不做。

但如果表达式评价为真,我们执行一组语句,如果表达式评价为假,我们能执行另一组语句吗? 当然可以!这就是else语句的作用。

if (expression)
{
    block of statement;
}
else
    statement;
  • 在 if 之后的语句组被称为 if 块。同样,在 else 之后的语句组成了 else 块。
  • else 只能在 if 块的下面。if 块中的语句和 else 块中的语句被缩进了右边。
  • 如果 if 块中只有一条语句要执行,而 else 块中只有一条语句,我们可以把这对括号去掉。
  • 和 if 语句一样,else 的默认范围也是紧接在 else 之后的语句。要覆盖这个默认范围,必须使用一对大括号。

3.3 嵌套多个 if-else

如果我们在 if 语句的正文或 else 语句的正文中写一个完整的 if-else 构造。这就是所谓的判断"嵌套"。 具体如下 -

if (expression1)
    statement;
else
{
    if (expression2)
        statement;
    else
    {
        block of statement;
    }
}

3.4 if-else 阶梯/else-if 从句

else-if 是最一般的多向决策的写法。

if(expression1)
    statement;
else if(expression2)
    statement;
else if(expression3)
    statement;
else if(expression4)
{
    block of statement;
}
else
    statement;

表达式 按顺序进行判断;如果一个表达式为真,则与之相关的 "语句" 或 "语句块" 将被执行,这就结束了整个链条。 同样,每个语句的代码要么是一个单独的语句,要么是用括号括起来的一组语句。 最后的 else部分处理的是 "上述条件中没有一个" 或 其他条件都不满足的默认情况。

3.5 switch 语句或控制语句

switch 语句是一种多向决策,它可以测试一个表达式是否匹配多个 常数整数值 中的一个,并相应地进行分支。 switch 让语句我们从众多的选择中做出决定,或者更正确的说法是switch-case-default, 因为这三个关键字共同构成了switch语句。

switch (expression)
{
    case constant-expression:
        statement1;
        statement2;
        break;
    case constant-expression:
        statement;
        break;
    ...
    default:
        statement;
}
  • 在 switch...case 命令中,每个 case 就像一个简单的标签。一个标签决定了程序中的一个点,程序的执行必须从那里继续执行。switch语句将选择其中的一个 case 部分或 标签 来继续执行程序。程序将继续执行,直到到达break命令为止。
  • break语句在 switch 结构中具有重要的作用。如果你删除了 break,程序的执行将继续到下一个 case 段,其余的所有 case 段都将被执行,直到switch块结束(而大多数时候,我们只希望一个case段被执行)。
  • 如果没有一个 case 部分与 switch 匹配,那么 default 部分将被执行。

3.6 switch 与 if-else 阶梯比较

以下情况无法使用 switch:

  • 不能用 switch 来测试 float 表达式
  • 不能有变量表达式(例如,case a+3 是错误的)
  • 多个 case 不能使用相同的表达式

4. 循环控制结构

有时我们希望我们的代码中的某些部分能够被执行不止一次。我们可以在程序中重复代码,或者使用循环。 很显然,如果我们需要重复执行代码的某些部分,比如说我们需要执行一百次,那么重复代码是不现实的。 但是,我们也可以用循环来重复执行代码。

有三种方法 for、while 和 do-while 来让我们重复执行程序中的一部分代码。

4.1 while loop

循环是由 条件或表达式 和 必须运行的单一命令或命令块 构成的:

//for single statement
while(expression)
    statement;

//for multiple statement
while(expression)
{
    block of statement
}

如果被测试的条件为 true,在 while 循环中的语句就会继续执行。 当条件变成假的时候,控制就会转移到 while 之后第一个语句。

while的一般形式如下:

initialise loop counter;
while (test loopcounter using a condition)
{
    do this;
    and this;
    increment loopcounter;
}

4.2 for loop

for 循环类似于 while 循环,但它更复杂。for循环是由 决定循环运行次数 的 控制语句 和 命令 部分组成的。 命令部分可以是单个命令,也可以是一个命令块。

//for single statement
for(control statement)
    statement;

//for multiple statement
for(control statement)
{
    block of statement
}

控制语句 本身有三个部分:

for ( initialization; test condition; run every time command )
  • initialization 部分只在 for 循环开始时执行一次。我们可以在这里初始化一个循环变量。
  • test condition 是循环中最重要的部分。如果这个条件有效(true),循环将继续运行。如果条件无效(false),循环将终止。
  • run every time command 部分将在每个循环周期中执行。我们通过这部分来达到终止循环的最终条件。例如,我们可以通过 增加 或 减少 循环变量的值的方式,在指定的循环次数后,让循环条件变得无效,for循环就可以终止。

4.3 do-while循环

while 和 for 循环在顶部测试终止条件。相比之下,C语言中的第三个循环,即 do-while,每次通过循环体后,在底部测试; 循环体总是至少执行一次。do-while 的语法是:

do
{
    statements;
}while (expression);

statements 被执行,然后对 expression 进行判断。如果为真,则再次执行 statements,以此类推。 当 expression 变为false时,循环终止。 经验表明,do-while 比 while 和 for 要少用得多。do-while 循环是为了保证循环内的语句至少执行一次。

4.4 break 和 continue 语句

前面我们在 switch...case 结构中使用了 break 语句。我们也可以在循环中使用 "break" 语句来终止循环并退出循环(有特定的条件)。 在下面的例子中,循环持续执行,直到 num>=20 或 输入的分数为负数。

while (num<20)
{
    printf("Enter score : ");
    scanf("%d",&scores[num]);
    if(scores[num]<0)
        break;
}

Continue 语句可以在循环中使用。像 break 一样 continue 也可以改变程序的 flow。 但是它不会终止循环,只是跳过当前循环的剩余语句,返回循环的起始点。

#include<stdio.h>
main()
{
    while((ch=getchar())!='\n')
    {
        if(ch=='.')
            continue;
        putchar(ch);
    }
}

在上面的例子中,程序接受所有的输入,但省略了其中的'.'字符。当你输入的时候,文字会被 echo, 但在你按下回车键(相当于插入一个"n"字符)后,主输出会被打印出来。getchar() 函数是一个缓冲输入函数。

4.5 Goto 和 标签

C语言提供了可以无限使用的 goto 语句,并提供了分支到的标签。goto 语句从来都不是必要的,在实际工作中, 如果没有 goto 语句,写代码几乎都很容易。我们在本书中没有使用 goto 语句。

然而,有一些情况下,goto 可能会有一席之地。最常见的情况是在一些深度嵌套结构中放弃处理, 比如同时断开两个或多个循环。不能直接使用 break 语句,因为它只能从最内层循环中退出:

for ( ... )
{
    for ( ... )
    {
        ...
        if (disaster)
        {
            goto error;
        }
    }
}
...
error:
/* clean up the mess */

如果错误处理代码不复杂,而且错误可能发生在几个地方,这种组织方式很方便。

标签的形式和变量名一样,后面有冒号。它可以和 goto 一样附加到任何一个函数中的任何语句上。 标签的范围是整个函数的范围。注意--通过使用 goto,程序变得不可靠,不可读,难以调试。 但是,许多程序员认为 goto 很诱人。

5. 数组

数组是容纳多个相同数据类型的变量的结构。数组中的第一个元素编号为0,所以最后一个元素的大小比数组的大小少 1。 数组也被称为下标变量。在使用数组之前,必须先声明数组的类型和维数。

5.1 数组的声明

像其他变量一样,为了让编译器知道我们想要一个什么样的数组和一个多大的数组 ,数组也需要提前声明。

int marks[30] ;

在这里,int 指定了变量的类型,就像普通变量一样,而 marks 指定了变量的名称。 但是 [30] 是新引入的。数字 30 告诉我们的数组中会有多少个 int 类型的元素。 这个数字通常被称为数组的 "维数"。括号 ( [] ) 告诉编译器,我们正在处理一个数组。

现在我们来看看如何在声明数组的同时初始化一个数组。下面是几个例子:

int num[6] = { 2, 4, 12, 5, 45, 5 } ;
int n[] = { 2, 4, 12, 5, 45, 5 } ;
float press[] = { 12.3, 34.2 -23.4, -11.3 } ;

5.2 访问数组中的元素

声明了一个数组之后,我们来看看数组中的单个元素如何被引用。 元素引用用下标来完成的,下标是指数组名称后面括号中的数字。这个数字指定了元素在数组中的位置。 所有的数组元素都是有编号的,从0开始,因此,marks[2]不是数组中的第二个元素,而是第三个元素。

int valueOfThirdElement = marks[2]

5.3 将数据输入到数组中

下面是将数据存入数组的代码部分:

for(i = 0;i <= 29;i++)
{
    printf("\nEnter marks");
    scanf("%d", &marks[i]);
}

for 循环会使用户查询和接收学生的分数的过程重复30次。第一次通过循环时,i的值为0, 所以 scanf() 函数将导致输入的值被存储在数组元素 mark[0] 中,即数组的第一个元素。 这个过程会重复进行,直到 i 变成 29。这是最后一次通过循环,这是一件好事,因为没有像mark[30]这样的数组元素。

在 scanf() 函数中,我们在数组的元素 marks[i] 上使用了 "address of "运算符(&)。 这样做,我们将这个特定的数组元素的地址传递给 scanf() 函数,而不是它的值;这正是 scanf() 所要求的。

5.4 从数组中读取数据

数组中读回数据,然后用它来计算平均分。循环的内容是一样的,但现在循环的主体是将每个学生的分数加到一个名为sum的变量中。 当所有的分数相加后,结果被除以30,即学生人数,得到平均分。

for ( i = 0 ; i <= 29 ; i++ )
    sum = sum + marks[i] ;
avg = sum / 30 ;
printf ( "\nAverage marks = %d", avg ) ;

5.5 例子

让我们试着写一个程序,找出班上30名学生考试的平均分数。

#include<stdio.h>
main()
{
    int avg, i, sum=0;
    int marks[30] ; /*array declaration */
    for ( i = 0 ; i <= 29 ; i++ )
    {
        printf ( "\nEnter marks " ) ;
        scanf ( "%d", &marks[i] ) ; /* store data in array */
    }
    for ( i = 0 ; i <= 29 ; i++ )
        sum = sum + marks[i] ; /* read data from an array*/
    avg = sum / 30 ;
    printf ( "\nAverage marks = %d", avg ) ;
}

6 字符串

6.1 什么是字符串?

字符串是由字符组成的数组,数组的每个成员都包含字符串中的一个字符。

例子:

#include<stdio.h>
main()
{
    char name[20];
    printf("Enter your name : ");
    scanf("%s",name);
    printf("Hello, %s , how are you ?\n",name);
}

输出结果:

Enter your name : Vineet
Hello, Vineet, how are you ?

如果用户输入 "Vineet",那么数组的第一个成员将包含 "V",第二个成员将包含 "i",以此类推。 C 语言通过一个零值字符来确定字符串的结尾,我们把这个字符称为 NULL。我们把这个字符称为 NULL字符, 并以 0 字符显示。(它只有一个字符,它的值是 0,但是我们用两个字符来显示它是为了记住它是一个字符类型,而不是整数)。

同样的,我们可以通过给每个成员分配字符值来组成这个字符串。

name[0]='B';
name[1]='r';
name[2]='i';
name[3]='a';
name[4]='n';
name[5]='\0';

正如我们在上面的例子中看到的,字符串变量的占位符是 %s。另外,我们在接收字符串值时不会使用 & 符号。

6.2 注意点

当使用scanf()输入字符串时,以下两点我们必须谨慎对待:

  • 字符串的长度不能超过字符数组的维数。这是因为C语言编译器不对字符数组进行边界检查。
  • scanf()不能接收多字字符串。因此像 "Vineet Choudhary "这样的名字是不能接受的。

绕过这个限制的方法是使用函数get()。函数get()和它的对应部分puts()的用法如下所示:

#include<stdio.h>
main( )
{
    char name[25] ;
    printf ("Enter your full name ") ;
    gets (name) ;
    puts ("Hello!") ;
    puts (name) ;
}

这里是输出.....

Enter your name Vineet Choudhary
Hello!
Vineet Choudhary

这个程序和输出是不言自明的,puts()一次只能显示一个字符串(因此在上面的程序中使用了两个puts()。 另外,在显示一个字符串时,与printf()不同,puts()会把光标放在下一行。虽然get()一次只能接收一个字符串, 但是get()的好处是它可以接收多字字符串。

如果不嫌麻烦的话,我们可以通过以下这样的方式让 scanf() 接受多字字符串:

char name[25] ;
printf ("Enter your full name ") ;
scanf ("%[^\n]s", name) ;

虽然这样也是可行的,但最好还是通过函数调用的方式。

6.3 标准库中的字符串函数

每个C语言编译器都在string.h文件中提供了大量有用的字符串处理库函数。

strlen - 查找字符串的长度
strlwr - 将字符串转换为小写
strupr - 将字符串转换为大写
strcat - 在另一个字符串的末尾添加一个字符串
strncat - 将一个字符串的前 n 个字符添加到另一个字符串
strcpy - 将字符串复制到另一个字符串中
strncpy - 将一个字符串的前 n 个字符复制到另一个字符串中。
strcmp - 比较两个字符串
strncmp - 比较两个字符串中的前n个字符。
strcmpi - 对两个字符串进行比较,不考虑大小写("i" 代表该函数忽略了case)
stricmp - 对两个字符串进行比较,不考虑大小写(与 strcmpi 一样)
strnicmp - 比较两个字符串的前n个字符,不考虑大小写
strdup - 复制一个字符串
strchr - 查找字符串中给定字符的首次出现的次数
strrchr - 查找字符串中给定字符的最后一次出现时间
strstr - 查找给定字符串在另一个字符串中的首次出现
strset - 将字符串的所有字符设置为给定字符
strnset - 将字符串中的前n个字符设置为给定字符
strrev - 反转字符串

7. 函数

7.1 什么是函数?

函数是由一个代码块组合而成,可以通过调用该代码块的名称来调用或在程序的任何地方使用。 函数的主体以 { 开头,以 } 结束。这与 main 函数类似。下面的例子显示了我们如何编写一个简单的函数:

#include<stdio.h>

/*Function prototypes*/
myfunc();

main()
{
    myfunc();
}

/*Function Defination*/
myfunc()
{
    printf("Hello, this is a test\n");
}

在上面的例子中,我们将程序的一部分放在一个独立的函数中。不过函数体可以很复杂。创建一个函数后, 我们可以用它的名字来调用它。函数也可以相互调用。一个函数甚至可以调用它自己。

顺便说一下,注意 function prototypes(函数原型) 部分。在一些 C 语言的编译器中, 我们需要在程序之前引入我们要创建的函数。引入一个函数就是被称为函数原型。请注意:

  • 任何C语言程序都至少包含一个函数
  • 如果一个程序只包含一个函数,那么它必须是 main()
  • 如果一个 C 程序包含一个以上的函数,那么其中的一个而且只有一个必须是 main(),因为程序的执行总是以 main() 开始
  • 一个C语言程序中的函数的数量没有限制
  • 程序中的每个函数都是按照 main() 中的函数调用的顺序来调用的
  • 在每个函数完成后,控制权返回到 main()。当 main() 中的函数调用用完后,程序结束。

7.2 为什么要使用函数

为什么要写单独的函数?为什么不把整个逻辑整合为一个函数,即main( )? 有两个原因:

  • 编写函数可以避免重复编写同样的代码
  • 使用函数可以让写程序变得更容易,并且可以跟踪它们在做什么。

如果一个程序的操作可以被分割成不同的活动,并将每个活动放在不同的函数中,那么每个活动都可以或多或少地独立编写和检查。 将代码分成模块化的函数,还可以使程序更容易设计和理解。把整个逻辑都塞进一个函数里是一种非常糟糕的编程风格。 相反,把一个程序分解成一个个小的单元,并为每一个孤立的子单元编写函数。即使写一些只被调用一次的函数也不要犹豫。 重要的是,这些函数要执行一些逻辑上独立的任务。

7.3 函数参数

函数能够以变量的形式接受输入参数,这些输入参数变量可以在函数体中使用:

#include<stdio.h>
/* use function prototypes */
sayhello(int count);
main()
{
    sayhello(4);
}

sayhello(int count)
{
    int c;
    for(c=0;c<count;c++)
        printf("Hello\n");
}

在上面的例子中,我们调用了参数为 4 的 sayhello() 函数。这个函数接收到一个输入值, 并在开始执行函数体之前将其分配给 count 变量。

7.4 函数的返回值

在数学中,我们一般期望一个函数返回一个值。它可以接受参数,也可以不接受参数,但它总是返回一个值。

这个返回值和 C 语言中的其他值一样有一个类型,它可以是int、float、char或其他任何类型。 这个返回值的类型决定了你的函数的类型。

函数的默认类型是 int 或整数。如果你没有指出你所使用的函数类型,那么它的类型将是int。 正如我们前面所说的,每个函数都必须返回一个值。我们通过 return 命令来实现:

int sum()
{
    int a,b,c;
    a=1;
    b=4;
    c=a+b
    reurn c;
}

上面的函数返回变量 c 的值作为函数的返回值。我们也可以在 return 命令中使用表达式。 例如,我们可以将函数的最后两行替换为返回 a+b;如果你在函数中忘记了返回值, 你会在大多数C语言编译器中得到一个警告消息。这个消息会警告你,你的函数必须返回一个值。 警告不会停止程序的执行,但错误会阻止程序的执行。

在我们之前的例子中,我们在函数中没有返回任何值。例如,你必须在 main() 函数中返回一个值。

main()
{
    .
    .
    .
    return 0;
}

int 类型函数的默认返回值是 0,如果你没有在 main() 函数中插入返回值 0 或其他任何值, 则会自动返回一个 0 值。如果你打算在你的函数中返回一个 int 值,最好在你的函数头中提到返回值,并在函数中写上。

7.5 void 返回值

C语言中还有一个 void 类型的函数。void 类型的函数是不返回值的函数。你可以把不需要返回值的函数定义为 void 类型的函数。

void test ()
{
    /* fuction code comes here but no return value */
}

void 函数不能被赋值给一个变量,因为它不返回值。所以不能这样写:

a=test();

使用上述命令会产生错误。

7.6 递归函数

在 C 语言中,函数是可以自己调用的。如果一个函数正文中的语句调用同一个函数,则称为递归函数。

下面是计算因式值的递归函数。

#include<stdio.h>
int rec(int);
main()
{
    int a, fact ;
    printf("\nEnter any number ");
    scanf ("%d", &a);
    fact = rec(a);
    printf ("Factorial value = %d", fact);
}
int rec (int x)
{
    int f;
    if (x == 1)
        return (1);
    else
        f = x * rec (x - 1) ;
    return (f) ;
}

输出:

Enter any number 5
Factorial value = 120

让我们来理解这个递归的因式函数。发生的事情是:

rec(5) returns(5 * rec(4),
 which returns (4 * rec(3),
  which returns (3 * rec(2),
   which returns (2 * rec(1),
    which returns (1)))))

即很难想象出控件是如何从一个函数调用到另一个函数的流程。也许下图可以让事情更清晰一些。

递归流程

7.7 多参数

事实上,你可以在一个函数中使用多个参数。下面的例子将告诉你如何做到这一点。

#include<stdio.h>
int min(int a,int b);
main()
{
    int m;
    m=min(3,6);
    printf("Minimum is %d",m);
    return 0;
}
int min(int a,int b)
{
    if(a<b)
        return a;
    else
        return b;
}

正如你所看到的,你可以很容易地将变量添加到参数列表中。

7.8 按值调用

C 语言编程语言中的函数调用,使用按值调用方法。让我们看一个例子来更好地理解这个话题。

#include<stdio.h>
void test(int a);
main()
{
    int m;
    m=2;
    printf("\nM is %d",m);
    test(m);
    printf("\nM is %d\n",m);
    return 0;
}
void test(int a)
{
    a=5;
}

在 main() 函数中,我们声明了一个变量 m,我们给 m 赋值 2。

程序输出的结果是:

M is 2
M is 2

所以你看函数调用并没有改变参数的值。这是因为函数调用方法只发送变量 m 的值给函数,而没有发送变量本身。 实际上,它将变量 m 的值放在一个叫做 堆栈 的内存位置,然后函数在没有访问主变量本身的情况下检索该值。 这就是为什么我们把这种调用方法称为 "按值调用"。

7.9 通过引用调用

还有一种发送变量的方法叫做 "引用调用"。这第二种方法可以让函数修改函数调用中使用的参数变量的值。

我们先看一个例子,然后再进行描述。

#include<stdio.h>
void test(int *ptr);
main()
{
    int m;
    m=2;
    printf("\nM is %d",m);
    test(&m);
    printf("\nM is %d\n",m);
    return 0;
}
void test(int *ptr)
{
    printf("\nModifying the value inside memory address%ld",&m);
    *ptr=5;
}

为了能够修改函数中作为参数的变量的值,函数需要知道变量的内存地址(变量在内存中的具体位置)。

在C语言中,& 操作符给出了变量的存储地址。例如,如果 m 是一个类型为 int 的变量,那么 &m 将给出变量的起始内存地址, 我们把这个地址称为指针。

ptr=&m ;

在上面的命令中,ptr 变量将包含变量 m 的内存地址。事实上,它将接收到的值放在函数中使用的变量的内存位置。 现在我们明白了为什么在 scanf 变量的变量名前加上 & 符号的原因了。

scanf("%d",&a) ;

现在我们有了变量的内存地址后,我们必须使用一个操作符来分配一个值或访问存储在该地址中的值。

正如我们所说的,我们可以使用 & 操作符找到变量 a 的地址。

ptr=&a ;

现在,我们可以使用 * 操作符找到存储在变量 a 中的值。

val=*ptr; /* finding the value ptr points to */

我们甚至可以修改地址里面的值。

*ptr=5 ;

让我们再看一下我们的例子。我们已经把指针(内存地址)传递给了函数。现在,函数可以修改存储在变量中的值。 如果你运行这个程序,你会得到这样的结果。

M is 2 Modifying the value inside memory address 2293620 M is 5

所以你看这一次我们改变了一个参数变量的值,在被调用的函数里面(通过修改我们的主变量的内存位置里面的值)。

8. 指针

8.1 什么是指针?

指针是一个包含了变量的地址的变量。最主要的是,一旦你能讨论一个变量的地址,那么你就可以访问该地址, 并检索存储在其中的数据。

8.2 C指针语法

指针需要一点新的语法,因为当你有一个指针时,你需要同时请求它存储的内存位置和存储在该内存位置的值。 因此,由于指针有一定的特殊性,所以当你声明指针变量时,需要告诉编译器这个变量是一个指针, 并告诉编译器它指向什么类型的内存。

指向器声明看起来是这样的。

<variable_type> *<name> ;

例如,你可以用下面的语法声明一个存储整数地址的指针。

int *points_to_integer ;

请注意 * 的使用。这是声明指针的关键;如果直接在变量名前加上它,就会声明该变量是一个指针。

8.3 指针符号

考虑到声明的内容。

int i = 3;

这个声明告诉C语言编译器

  • 在内存中预留空间来保存整数值。
  • 将名称 i 与这个内存位置关联起来。
  • 将值 3 存储在这个位置。
  • 我们可以用下面的内存图来表示 i 在内存中的位置。
内存

我们看到,计算机选择了内存位置 65524 作为存储值 3 的位置。这个位置号 65524 并不是一个值得依赖的数字, 因为在其他时间,计算机可能会选择不同的位置来存储值 3。不过重要的一点是,i 在内存中的地址是一个数字。 我们可以通过下面的程序打印出这个地址号。

#include<stdio.h>
main()
{
    int i = 3 ;
    printf("\nAddress of i = %u",&i);
    printf("\nValue of i = %d",i);
}

上述程序的输出是

Address of i = 65524
Value of i = 3

仔细看第一个 printf()语句,这个语句中使用的 & 是 C 的 "address of" 操作符。表达式 &i 返回变量i的地址, 在本例中恰好是 65524。因为 65524 代表一个地址,所以不存在与符号相关的问题。因此,我们使用 %u 打印出来, %u 是打印无符号整数的格式指定符。我们一直在 scanf() 语句中使用 & 操作符。

C语言中的另一个指针操作符是 * 叫做 value at address 操作符。 它给出了存储在某个特定地址的值。 value at address 操作符也被称为 indirection 操作符。

仔细观察以下程序的输出。

#include<stdio.h>
main()
{
    int i = 3 ;
    printf("\nAddress of i = %u",&i);
    printf("\nValueof i = %d",i);
    printf("\nValue of i = %d",*(&i));
}

上述程序的输出为:

Address of i = 65524
Value of i = 3
Value of i = 3

请注意,打印 *(&i) 的值与打印 i 的值是一样的,表达式 &i 给出了变量 i 的地址。

j = &i ;

但是请记住,j 不是像其他整数变量一样是一个普通的变量。它是一个包含其他变量(本例中的 i )的地址的变量。 因为 j 是一个变量,所以编译器必须在内存中为它提供空间。再一次,下面的内存图将说明 i 和 j 的内容。

内存

可以看到,i 的值是 3,j 的值是 i 的地址。但是稍等,我们不声明 j,就不能使用 j。 而且由于 j 是一个包含 i 的地址的变量,所以要声明为。

int *j ;

这个声明告诉编译器,j 将被用来存储一个整数值的地址。换句话说,j 指向一个整数值。 我们如何证明声明中的 * 的用法是合理的。

int *j ;

让我们根据 * 的含义来理解。它代表 "地址上的值"。因此,int *j 的意思是,在 j 的地址中的值是一个 int。

8.4 例子

下面是一个程序,展示了我们一直在讨论的关系。

#include<stdio.h>
main( )
{
    int i = 3 ;
    int *j ;
    j = &i ;
    printf ("\nAddress of i = %u",&i) ;
    printf ("\nAddress of i = %u",j) ;
    printf ("\nAddress of j = %u",&j) ;
    printf ("\nValue of j = %u",j) ;
    printf ("\nValue of i = %d",i) ;
    printf ("\nValue of i = %d",*(&i)) ;
    printf ("\nValue of i = %d",*j) ;
}

上述程序的输出是

Address of i = 65524
Address of i = 65524
Address of j = 65522
Value of j = 65524
Value of i = 3
Value of i = 3
Value of i = 3

9. 结构

结构是由一个或多个变量组成的集合,不同类型的变量,为了方便处理,用一个单一的名称组合在一起。

9.1 结构的声明

结构声明语句的一般形式如下。

struct <structure name>
{
    structure element 1;
    structure element 2;
    structure element 3;
    ......
    ......
    structure element n;
};

一旦定义了新的结构数据类型,一个或多个变量可以被声明为该类型。

例如变量 b1、b2、b3 可以被声明为结构类型。

struct book
{
    char name;
    float price;
    int pages;
};

声明为:

struct book b1, b2, b3 ;

这条语句在内存中预留了空间。它为结构中的所有元素留出了可用的空间--在本例中, 7个字节--1个分配给 name,4个分配给 price,2个分配给 pages。这些字节总是在相邻的内存位置。

与 主变量 和 数组 一样,结构变量也可以在声明的地方进行初始化。所使用的格式与初始化数组时使用的格式非常相似。

struct book
{
    char name[10];
    float price;
    int pages;
};

struct book b1 = { "Basic", 130.00, 550 } ;
struct book b2 = { "Physics", 150.80, 800 } ;

9.2 访问结构元素

在数组中,我们可以使用下标访问数组中的单个元素。结构体使用不同的方案。它们使用了一个点(.)运算符。 因此,要引用 book 结构的变量中定义的 pages,我们必须使用:

b1.pages

同理,要引用 pages:

b1.price

注意,点之前必须有一个结构变量,而点之后必须有一个结构元素。

9.3 示例

下面的例子说明了这个数据类型的使用。

#include<stdio.h>
main()
{
    struct book
    {
        char name;
        float price;
        int pages;
    };
    struct book b1, b2, b3 ;

    printf("\nEnter names, prices & no. of pages of 3 books\n");
    scanf("%c %f %d", &b1.name, &b1.price, &b1.pages);
    scanf("%c %f %d", &b2.name, &b2.price, &b2.pages);
    scanf("%c %f %d", &b3.name, &b3.price, &b3.pages);

    printf("\n\nAnd this is what you entered");
    printf("\n%c %f %d", b1.name, b1.price, b1.pages);
    printf("\n%c %f %d", b2.name, b2.price, b2.pages);
    printf("\n%c %f %d", b3.name, b3.price, b3.pages);
}

下面是输出结果.....

Enter names, prices and no. of pages of 3 books
A 100.00 354
C 256.50 682
F 233.70 512

And this is what you entered
A 100.000000 354
C 256.500000 682
F 233.700000 512

9.4 总结

  • 当我们希望将不同的数据存储在一起时,通常会使用结构
  • 结构元素可以通过结构变量使用点(.)运算符访问结构元素
  • 结构元素可以使用箭头(->)运算符通过指向结构的指针来访问结构元素
  • 一个结构变量的所有元素都可以通过赋值(=)运算符分配给另一个结构变量
  • 可以通过值或地址将结构变量传递给一个函数
  • 可以创建一个结构数组

10. 文件

10.1 文件指针

在C语言中使用文件的方法有很多,最直接的使用方法是通过文件指针。

FILE *fp;

fp 是一个指向文件的指针。

FILE 类型不是基本类型,而是在头文件 stdio.h 中定义的,这个文件必须包含在你的程序中。

10.2 打开一个文件

fp = fopen(filename, mode);

filename 和 mode 都是字符串。mode 可以是:

  • r - 读
  • w - 写入,若存在,则覆盖
  • a - 写入,若存在,则附加
  • r+ - 读和写,若存在,不覆盖
  • w+ - 读和写,若存在,覆盖
  • a+ - 读和写,若存在,附加
  • b - 可以附加到上述任何一个中,以强制以二进制模式而不是文本模式打开文件
  • fp = fopen("data.dat", "a"); - 将打开磁盘文件data.dat进行写入,任何写入的信息都将附加到文件中

下面是 ANSI C Rationale 中有用的表格,列出了不同模式下打开文件的不同操作和要求。

文件操作

如果文件不能以请求的模式打开,fopen 会返回NULL。在尝试访问文件之前,应该检查返回的值。 下面的代码显示了如何检查fopen返回的值。当文件不能被打开时,会打印一个合适的错误信息,程序停止。 但是在大多数情况下这样处理是不合适的,应该给用户重新输入文件名的机会。

#include <stdio.h>
int main()
{
    char filename[80];
    FILE *fp;
    printf("File to be opened? ");
    scanf("%79s", filename);
    fp = fopen(filename,"r");
    if (fp == NULL)
    {
        fprintf(stderr, "Unable to open file %s\n", filename);
        return 1; /* Exit to operating system */
    }
    //code that accesses the contents of the file
    return 0;
}

顺序文件访问是通过以下库函数执行的。

  • fprintf(fp, formatstring , ...) - 打印到一个文件。
  • fscanf(fp, formatstring , ...) - 从文件中读取
  • getc(fp) - 从文件中获取一个字符
  • putc(c, fp) - 将一个字符放到文件中
  • ungetc(c, fp) - 将一个字符回推到文件上(只保证一个字符可以回推)
  • fopen( filename , mode) - 打开一个文件
  • fclose(fp) - 关闭一个文件

标准头文件 stdio.h 定义了三个文件指针常数,stdin、stdout 和 stderr,用于标准输入流、输出流和错误流。 将错误信息写到标准错误流中被认为是很好的做法。

使用 fprintf() 函数来完成这个操作。

fprintf(sterr, "ERROR: unable to open file %s\n", filename);

fscanf() 和 getc() 函数用于顺序访问文件,也就是说,后续的读取将在刚刚读取的数据之后读取数据, 最终会到达文件的终点。如果你想在文件中向前或向后移动,或者从开始、结束或当前位置跳转到特定的偏移量, 可以使用 fseek() 函数(尽管文件必须以二进制模式打开)。函数 ftell() 报告从文件的起始位置开始的当前偏移量。

如果你想混合读和写到同一个文件,每次从读到写的切换(或反之亦然)必须在先行调用 fseek()、fsetpos()、rewind() 或 fflush() 。如果需要保持在文件中的同一位置,则使用 fflush() 或 fseek(fp,0L,SEEK_CUR), 它会从当前位置移动0个字节!

11. 命令行参数

我们在命令提示符中传递给main()的参数被称为``命令行参数``。main的完整声明是这样的:

int main (int argc, char *argv[])

函数main()可以有两个参数,传统上命名为 argc 和 argv。其中,argv 是一个指向字符串的数组,argc是一个int, 其值等于argv指向的字符串数。当程序被执行时,命令行上的字符串被传递给main()。更准确地说, 命令行的字符串被存储在内存中,第一个字符串的地址存储在argv[0],第二个字符串的地址存储在argv[1], 以此类推。参数argc被设置为命令行给出的字符串数。

例如,在我们的示例程序中,如果在命令提示符下,我们给出,

filecopy PR1.C PR2.C

其中,argc 存储 3

  • argv[0] - 将包含字符串 "filecopy" 的基本地址。
  • argv[1] - 将包含字符串 "PR1.C" 的基本地址。
  • argv[2] - 将包含字符串 "PR2.C" 的基本地址。