C语言程序设计基础(译美版)上

C 语言

第一章 导言

1.1入门

  • printf是一个用于打印输出的库函数

  • 用双引号括起来的字符串序列称为字符串或字符串常量

    转义字符序列

  • ​ \n 换行

  • ​ \t 表示制表符

  • ​ \b 表示回退符

  • \ " 表示双引号

  • \表示的是反斜杠本身

#include<stdio.h>   // 包含标准库的信息
int main() {
	printf("hello,world\n");
	printf("hello,world");
	printf("hello,world");
}
// 在老版中 直接写main()  现新版中int 已经不可省略
//  \n代表的是换行

1.2变量和算术表达式

在C语言中没有布尔类型

intfloatcharshortlongdouble
整型浮点数一个字符短整型长整型双精度浮点型
#include <stdio.h>
int main() {
	float fahr, celsius;
	int lower, upper, step;

	lower = 0;
	upper = 300;
	step = 20;
	fahr = lower;
	while (fahr <= upper)     // while循环中 大括号起来的是一条或者多条语句   也可以是不括起来的单条语句
	{
		celsius = (5.0 / 9.0) * (fahr - 32.0);
		printf("%3.0f %6.1f\n", fahr, celsius);
		fahr = fahr + sftep;
	}
}
  • 在printf函数中 每个百分号表示其他的参数
%d按照十进制打印
%6d按照十进制打印并且至少6个字符宽
%f按照浮点数打印
%6f按照浮点数打印并且至少6个字符宽
%.2f’按照浮点数打印并且小数点后有两个小数
%6.2f浮点数打印 6个字符 小数点后有两个小数
%o八进制
%X十六进制
%C字符
%S字符串
%%%本身

1.3 for 语句

#include<stdio.h>
int main() {
	int father;
	for (father  = 0;  father<=300; father=father + 20)
		printf("%3d %6.1f\n", father, (5.0 / 9.0) * (father - 32));
	
}

1.4符号常量

  • #define 名字 替换文本
  • 符号常量一般使用大写字母进行拼写 而变量一般使用小写字母进行拼写 和java中的驼峰命名有所区别
  • 且#define指令行的末尾没有分号
#include<stdio.h>

#define LOWER 0
#define UPPER 300
#define STEP  20  

int main() {
	int father;
	for (father = LOWER; father <= UPPER; father = father + STEP)
		printf("%3d %6.1\n", father, (5.0 / 9.0 )* (father - 32));
} 

1.5字符输入和输出

标准库中提供了一次读写一个字符的函数 其中最简单的是getchar putchar

1.5.1文件复制

字符在键盘,屏幕或者其他的地方无论以什么形式表示,它在及其内部都是以位模式储存的,char类型专门储存这种字符型数据,当然任何整型(int)也可以用于储存字符型数据,由于一些潜在的重要原因,我们在此使用int 类型

EOF定义在头文件中 是一个整型数

#include <stdio.h>
int main() {
	int c;
	while ((c = getchar()) != EOF)
		putchar(c);
}
#include <stdio.h>
int main() {
    printf("EOF 的值为:%d \\n", EOF);
    return 0;
}
  • 如果 getchar() 读取到一个字符,那么它的返回值就是这个字符的ASCII码值(一个非负整数),此时 getchar() != EOF 的值为 1,表示“真”。
  • 如果 getchar() 读取到文件结束符(EOF),那么它的返回值就是 EOF,此时 getchar() != EOF 的值为 0,表示“假”。
  • EOF是一个常量,在 C 语言中通常被定义为-1
1.5.2字符计数
#include<stdio.h>
int main() {
	double nc;
	for (nc = 0; getchar() != EOF; ++nc);
	printf("%.0f\n", nc);
}
1.5.3行计数
  • 在表示逻辑关系的时候使用双等于号 ==
  • ASCII字符集中每个符号表示一个值
#include <stdio.h>

int main() {
	int c, nl;
	nl = 0;
	while ((c = getchar()) != EOF)
		if (c == '\n')
			++nl;
	printf("%d\n", nl);
}
练习1

统计空格,制表符与换行符个数的程序

#include <stdio.h>
int main() {
    int spaceCount = 0, tabCount = 0, newlineCount = 0;
    int c;
    while ((c = getchar()) != EOF) {
        if (c == ' ') {
            spaceCount++;
        }
        else if (c == '\t') {
            tabCount++;
        }
        else if (c == '\n') {
            newlineCount++;
        }
    }
    printf("空格个数: %d\n", spaceCount);
    printf("制表符个数: %d\n", tabCount);
    printf("换行符个数: %d\n", newlineCount);
    return 0;
}

​ 在终端(黑框)中手动输入时,系统并不知道什么时候到达了所谓的“文件末尾”,因此需要用<Ct+c>组合键然后按 Enter 键的方式来告诉系统已经到了EOF,这样系统才会结束while.

练习2

将输入复制到输出的程序,并将其中连续的多个空格用一个空格进行代替

首先,题目让我们把输入复制到输出,实际上就是输入什么就打印什么。多个空格用一个空格代替,这个我们先设置一个变量并初始为零,再判断输入的字符是不是空格。如果是空格,再判断下一个字符是不是空格。如果是,这个变量就加一,如果不是,这个变量就归零。再判断这个变量是否大于1。如果大于一,输出空,否则就输出下一个字符。

#include <stdio.h>
int main()
{
	int SpaceNum, word;
	SpaceNum = 0;
	while ((word = getchar()) != EOF)
	{
		if (word == ' ')
		{
			++SpaceNum;
		}
		else {
			SpaceNum = 0;
		}

		if (SpaceNum > 1)
		{
			;
		}
		else {
			putchar(word);
		}
	}
	return 0;
}

练习3

编写一个将输入复制到输出的程序,并将其中的制表符替换成\t,将回退符退换成\b,将反写杠替换成 \

#include <stdio.h>

int main()
{
	int word;
	while ((word = getchar()) != EOF)
	{
		if (word == '\t')
			printf("\\t");
		if (word == '\\')
			printf("\\\\");
		if (word == '\b')
			printf("\\b");

		if (word != '\t' && word != '\\' && word != '\b')
			putchar(word);
	}
	return 0;
}
1.5.4单词计数
#include <stdio.h> // 引入标准输入输出库,用于getchar和printf等函数

#define IN 1 // 定义宏IN,代表单词内部的状态
#define OUT 0 // 定义宏OUT,代表单词外部的状态

int main() {
    int c, n1, nw, nc, state; // 声明变量c用于读取字符,n1用于计数行数,nw用于计数单词数,nc用于计数字符数,state用于记录当前状态
    state = OUT; // 初始状态设置为OUT,表示在单词外部
    n1 = nw = nc = 0; // 初始化行数、单词数和字符数计数器为0

    while ((c = getchar()) != EOF) { // 循环读取字符直到文件结束符EOF
        ++nc; // 每读取一个字符,字符数nc增加1
        if (c == '\\n') // 如果读取的字符是换行符
            ++n1; // 行数n1增加1

        // 如果读取的字符是空格、换行符或制表符,则状态设置为OUT
        if (c == ' ' || c == '\\n' || c == '\\t')
            state = OUT;

        // 如果当前状态是OUT,并且读取的字符不是空格、换行符或制表符,则表示遇到了一个新单词
        else if (state == OUT) {
            state = IN; // 将状态设置为IN,表示现在在单词内部
            ++nw; // 单词数nw增加1
        }
    }

    printf("%d %d %d\\n", n1, nw, nc); // 打印行数、单词数和字符数
}

1.6 数组

#include <stdio.h> // 引入标准输入输出库

int main() {
    int c, i, nwhite, nother; // 声明变量c用于读取字符,i用于循环计数,nwhite用于计数空白字符,nother用于计数其他字符
    int ndigit[10]; // 声明一个数组ndigit,包含10个整数,用于计数0到9每个数字字符出现的次数
    
    nwhite = nother = 0; // 初始化空白字符和其他字符的计数器为0
    for (i = 0; i < 10; ++i) // 初始化数字字符的计数器数组
        ndigit[i] = 0;
    
    while ((c = getchar()) != EOF) { // 循环读取字符直到文件结束符EOF
        if (c >= '0' && c <= '9') // 如果读取的字符是数字
            ++ndigit[c - '0']; // 增加对应数字的计数器(通过字符减去'0'得到数组索引)
        else if (c == ' ' || c == '\\n' || c == '\\t') // 如果读取的字符是空格、换行符或制表符
            ++nwhite; // 增加空白字符的计数器
        else
            ++nother; // 对于其他所有字符,增加其他字符的计数器
    }
    
    printf("digits="); // 打印标签digits=
    for (i = 0; i < 10; ++i) // 循环打印每个数字字符的计数
        printf("%d ", ndigit[i]);
    printf(",white space=%d ,other =%d\\n", // 打印空白字符和其他字符的总数
        nwhite, nother);
    
    return 0; // 程序结束
}

1.7 函数

C语言中的函数等价于Fortran语言中的子程序或函数,也等价于Pascal语言中的过程或函数,C语言中没有提供求幂的运算符 我们现在测试

#include <stdio.h> // 引入标准输入输出库
int power(int m, int n); // 声明 power 函数

int main() {
    int i;
    for (i = 0; i < 10; ++i) // 循环 10 次
        printf("%d %d %d \\n", i, power(2, i), power(-3, i)); // 打印当前循环次数、2 的 i 次幂和-3 的 i 次幂
    return 0;
}

int power(int base, int n) { // 定义 power 函数
    int i, p;
    p = 1;
    for (i = 1; i <= n; ++i) // 循环 n 次
        p = p * base; // 每次循环将 p 乘以 base
    return p;
}

1.8 参数-传值调用

在C语言中 ,所有的函数参数都是通过值传递的,也就是说,传递给被调用函数的参数值存放在临时的变量中,而不是存放在原来的变量中。

1.9字符数组

  • int getline(char s[] ,int lim)
  • 他把第一个参数S声明为数组,把第二个参数lim声明为整型,声明中提供数组大小的目的就是留出储存空间
  • 有些函数返回有用的值,而有些函数仅用于执行一些动作,并不返回值,例如copy函数的返回值类型为void,它的显式说明该函数不返回任何值
#include<stdio.h> // 引入标准输入输出库
#include<stdlib.h> // 引入标准库,用于system函数
#define MAXLENGTH 100 // 定义最大行长度为100

int getline(char line[], int maxline); // 声明getline函数
void copy(char to[], char from[]); // 声明copy函数

int main()
{
    int currentLength;
    int maxLengthSoFar;

    char line[MAXLENGTH]; // 用于存储当前读取的行
    char longest[MAXLENGTH]; // 用于存储最长的行

    maxLengthSoFar = 0;
    while ((currentLength = getline(line, MAXLENGTH)) > 0) // 循环读取每一行
    {
        if (currentLength > maxLengthSoFar) // 如果当前行长度大于已知最长行长度
        {
            maxLengthSoFar = currentLength; // 更新最长行长度
            copy(longest, line); // 将当前行复制到最长行变量中
        }
    }

    if (maxLengthSoFar > 0) // 如果找到了至少一行文本
    {
        printf("%s", longest); // 打印最长的行
    }
    system("pause"); // 在Windows系统上暂停程序执行,等待用户按键
    return 0;
}

int getline(char line[], int maxline)
{
    int c;
    int i;
    for (i = 0; i <= maxline && (c = getchar()) != EOF && c != '\\n'; i++) // 读取字符直到EOF或换行符
    {
        line[i] = c; // 将读取的字符存储到line数组中
    }
    if (c == '\\n') // 如果读取到换行符
    {
        line[i] = c; // 将换行符也存储到line数组中
        i++;
    }
    line[i] = '\\0'; // 在字符串的末尾添加空字符,表示字符串的结束
    return i; // 返回读取的字符数
}

void copy(char to[], char from[])
{
    int i;
    i = 0;
    while ((to[i] = from[i]) != '\\0') // 将from数组中的字符复制到to数组中,直到遇到空字符
    {
        i++;
    }
}

1.10外部变量和作用域

  • 外部变量必须定义在所有函数之外,且只能定义一次
  • 在函数中使用extern类型的声明,这种类型的声明除了在前边加了一个关键字extern外,其他地方与普通变量的声明相同
  • 在某些情况下是可以省略extern声明,在源文件中,如果外部变量的定义都放在源文件的开始处,这样就可以省略extern声明
  • 如果程序包含在多个源文件中,而某个变量在file文件中定义,在file2与file3文件中使用,那么2与3就需要使用extern声明来建立该变量与其定义之间的联系,人们通常把变量和函数的声明放在单独的文件中,习惯上称之为头文件,并在每个源文件的开头使用。Include语句将所要用的头文件包含进来后缀名.h为头文件的扩展名。

第二章 类型 、运算符与表达式

2.1变量

  1. 第一个字符必须为字母 且下划线_被看做字母 通常用于命名较长的变量名 以提高其可读性
  2. 变量名不要以下划线开头
  3. 变量名都使用小写 常量都使用大写字母
  4. 局部变量一般都用较短的变量名 外部变量都使用较长的名字

2.2数据类型及长度

  • C语言只提供了下列几种基本数据类型
  • char 字符型 占用一个字节,可以存放本地字符集中的一个字符
  • int 整型 float单精度浮点型 double 双精度浮点型
  • 这些基本数据类型的前边还可以加上一些限定符
  • short int sh; long int counter (但一般下int可以省略)
  • unsigned long 无符号长整型

2.3常量

常量表达式是仅仅包含常量的表达式,在编译时求值,运行时不求值。

  • 一个字符常量是一个整数,书写时将一个字符扩在单引号中,如’0’,在ASCII中他的值就是48,他此时就与0没关系,增加了程序的易读性

  • \a =====>响铃符 \r =====>回车符 \f ======>换页符 \b ======> 回退符

  • \t =====>制表符(横向) \v =====>制表符(纵向) \ ======>反斜杠 ? ======>问号

  • ’ =======>单引号 " =======>双引号 \ooo =====>八进制数 \xhh ======>十六进制

  • '\0’表示值为0的字符 也就是空字符Null

字符串

字符串常量又叫字符串字面量,从技术角度来看它就是字符数组,它的内部使用了一个空字符’\0’作为串的结尾,因此存储在物理存储单元中实际要比双引号中多一位,标准库函数strlen(s)可以返回字符串的长度 ,但长度不包括结尾的空字符串

枚举常量

  • 另一种类型的常量,枚举是一个常量整型的数值的列表
  • enum boolean {NO , YES}; 第一个值为0,第二个值为1 依次类推 后边的值将向后递增
  • 不同枚举中的名字必须不同,同一枚举中不同的名字可以具有相同的值

2.4声明

所有的变量都必须先声明后使用,一个声明指定一种变量类型,后边所带的变量表可以包含一个或多个该类型的声明

任何变量的声明都可以使用const限定符限定,该限定符指定的变量的值不能被修改,对数组而言,const限定符指定数组所有元素的值都不能被修改

const double e=2.1=71828182845905;
const char msg[]="warning:";

const限定符也可以配合数组参数使用,它表明函数不能修改数组元素的值:

2.5三个运算符

  • 算术运算符 + - * / %
  • 关系运算符 > >= < <= == !=
  • 逻辑运算符 || &&

2.7类型转换

该表达式能够计算出s[i]中存储的字符所对应的数字值,这是因为’0’,'1’等在字符集中对应的数值是一个连续的递增序列

s[i]-'0'

C语言中,很多情况下会进行隐式的算术类型转换,一般来说,如果二元运算符(具有两个操作数的运算符成为二元运算符,比如+或*)的两个操作数具有不同的类型,那么在进行运算之前先要把较低的类型升为较高的类型,运算的结果为较高的类型

在没有unsigned类型的操作数

  • long double (另一个被转)
  • double
  • float
  • 将char与short的操作数转换为int 类型
  • long
  • 表达式中float的操作数不会转为double

在包含unsigned中 转换规则要复杂一些

假定int类型占16位,long占32位,那么-1L<1U,因为unsighed int 类型的1U被提升为signed long

和JAVA一样也可以在某些时候使用类型强转

2.8自增自减

n++ 先用后加 ++n先加后用

n–先用后减 --n先减后用

2.9运算符

C语言中提供了六个位操作运算符 这些运算符只能作用与整型操作数,即只能作用于带符号或无符号的char,short,int,long类型

  • & 按位与 AND
  • ! 按位或 OR
  • ^ 按位异或XOR
  • << 左移 >>右移
  • ~按位取反

按位与 & 符号常用于屏蔽某些二进制位

n  = n& 0 177;

这句话的意思是将除7个低2进制位外的其他位都设置为0

x=x | SET_ON;

这句话的意思是将那些二进制位置设置为1

移位运算符<<与>> 分别用于将运算的左操作符左移与右移,移动的位数则由右操作数指定(右操作数必须是非负值),因此,表达式x<<2将把X的值左移2位,右边空出的2位用0填补,该表达式等价于对左操作数乘4,在对Unsigned类型的无符号位进行右移时,左边空出的部分将用0填补,当对signed类型的带符号值进行右移时,某些及其将对左边空出的部分用符号位填补,而另一些机器则对左边空出的部分用0填补。

2.10赋值运算符与条件表达式

赋值运算符 +=

i+=2

条件表达式

三元运算符

erpr1 ? expr2 : expr3

2.11运算符的优先级和结合性

![](C:\Users\86159\Desktop\md笔记\C\YUNSUANFUYOUXIANJI.jpg

第三章 控制流

3.1语句与程序块

在c语言中,分号是语句结束符,而Pascal等语言却把分用作语句之间的分隔符。

用一对花括号{ }把一组声明和语句扩在一起就构成了一个复合语句(也叫做程序块),复合语句在语法上等价于单条语句,右花括号用于结束程序块,其后不需要分号。

3.2 if-else语句

在有if语句嵌套的时候使用花括号,因为else会默认匹配最近的if 容易产生歧义

3.3 else-if语句

C语言和Java中的if结构在基本形式上是相似的,都是用来实现分支结构的流程控制语句。不过,两者在细节上存在一些区别,以下是它们之间的主要不同点:

  1. 布尔值的判定:
    • C语言:在C语言中,if语句的条件判断比较灵活。任何非零值都被认为是true,而0值被认为是false。这意味着不仅布尔表达式可以作为条件,任何能够转换为整型的表达式都可以。
    • Java:Java中的if语句要求条件必须是布尔类型(boolean),只能是truefalse。这意味着在Java中不能使用非布尔类型的值(如整数1或0)直接作为条件。
  2. 语法严格性:
    • C语言:C语言的语法相对宽松,允许一些简写形式。例如,在for循环中,可以省略某些部分,而在if语句中,即使条件表达式后面没有括号,代码也是合法的(尽管不推荐)。
    • Java:Java的语法更加严格,要求if语句的条件必须用括号括起来。例如,if(x > 0) 是合法的Java代码,而在C语言中,if(x > 0)if x > 0 都是合法的。
  3. 赋值操作在条件判断中的使用:
    • C语言:在C语言中,赋值操作在条件判断中是合法的,但通常是一个错误,因为赋值操作会返回赋值的结果。例如,if(x = 1) 会将x赋值为1,然后判断1(非零)为真。
    • Java:在Java中,如果尝试在if条件中使用赋值操作(如if(x = 1)),编译器会报错,因为Java要求条件必须是布尔表达式。
int x = 1;
if(x = 1) { // 错误的做法,但C语言允许
    printf("This is not recommended in C.\\n");
}

int x = 1;
if(x = 1) { // 编译错误,不允许在Java中这样做
    System.out.println("This is not allowed in Java.");
}

3.4switch语句

跳出switch语句最最常用的方法是使用break语句和return语句,一个良好的程序设计风格,在switch语句最后一个分支(即default分支的后边)也加上一个break语句,可以有效防止后续添加其他分支的时候产生错误

switch (表达式) {
    case 常量表达式1:
        // 当表达式的值等于常量表达式1时,执行的代码块
        break; // 可选的,用于跳出switch语句
    case 常量表达式2:
        // 当表达式的值等于常量表达式2时,执行的代码块
        break; // 可选的,用于跳出switch语句
    /* ... */
    default:
        // 当表达式的值不匹配任何case时,执行的代码块
}

3.5 while循环与for 循环

while(表达式)

​ 语句

如果表达式值为真,就执行语句,直到表达式值为假,就执行语句后边的部分。

for循环如果省略表达式一和表达式三它就退化成while循环

3.6 do-while循环

  • while和for这两种循环在循环体执行前都会对终止条件进行测试,而do-while则不同,它先运行一次,然后再对终止条件进行测试。

3.7 break 和continnue

  • break:跳出循环
  • continue:跳出本次循环

3.8goto

  1. 标签定义:在goto语句之前,必须有一个标签定义,并且标签定义必须紧跟在语句之后。
  2. 跳转规则goto语句会导致程序跳转到标签所在的位置继续执行。
  3. 使用场景
    • 跳出多层循环goto可以用来跳出多层循环,而不仅仅是最近的循环。
    • 错误处理:在错误处理中,goto可以用来跳转到错误处理代码块。
    • 代码重构:在重构代码时,goto可以用来跳转到某个特定的代码段。

但是一般情况下更难理解 所以尽量不去使用goto语句进行编程

#include <stdio.h>

int main() {
    int i;

    // 循环
    for (i = 0; i < 10; i++) {
        if (i == 5) {
            goto exit_loop; // 当i等于5时,跳转到标签exit_loop
        }
        printf("%d ", i);
    }

exit_loop:
    printf("\\n跳出循环。\\n");

    return 0;
}

第四章 函数与程序结构

4.1函数的基本知识

4.2返回非整型值的函数

  • 函数与调用它的主函数在同一源文件中,并且类型不一致时,编译就会发现该错误;
  • **隐式声明:**如果未声明过的一个名字出现在某表达式中,并且其后紧跟左圆括号,那么上下文会认为这是一个函数名,其返回值会被假定为int,其参数不做任何假设;
  • return (表达式);表达式的值返回时会被转换为函数类型,这可能会丢失信息有些编译器会警告,故可显示类型转换;

4.3外部变量

  • external与internal是相对的,internal是用于描述定义在函数内部的函数参数及变量。外部变量定义在函数之外
  • 而外部变量可能会导致程序中各个函数之间具有太多的数据联系。
  • 而外部变量与内部函数相比具有更大的作用域和更长的生存周期,自动变量只能在函数内部使用

4.4作用域规则

  • 构成C语言程序的函数与外部变量可以分开进行编译,一个程序可以存放在几个文件中,原先已编译过的函数可以从库中加载
  • 名字的作用域值得是程序中可以使用该名字的部分,对于在函数开头声明的自动变量来说,其作用域是声明该变量名的函数。

外部变量或函数的作用域从声明它的地方开始:

int main(){...}

int sp=0;
double val[MAXVAL];

void push(double f) {....}
double pop (void)  {.....}
  • 那么,在这段代码中,push和pop这两个函数不需要进行任何声明就可以通过名字访问变量sp与val。但是,这两个变量名不能用在main中,push与pop函数也不能用在main函数中。
  1. 另外如果要在外部变量的定义之前使用该变量,或者外部变量的定义与变量的使用不在同一个源文件中,则必须在相应的变量声明中强制性地使用关键字ertern。
  2. 一个外部变量只能在某个文件中定义一次,而其他文件可以通过extern声明来访问它。外部变量的定义必须指定数组的长度,但extern声明则不一定要指定数组的长度。
  3. 外部变量的初始化只能出现在其定义中

4.5头文件

下面我们来考虑将上述计算机程序分割到若干个源文件中的情况,如果该程序的各组长部分很长

那么我们这样分割

  1. 将主程序main单独放在单文件main.c中
  2. 将push与pop函数以及他们使用的外部变量放在第二个文件stack.c中
  3. 将getop函数放在第三个文件getop.c中
  4. 将getch与ungetch函数放在第四个文件getch.c中
  5. 之所以分割成多个文件,主要考虑在实际程序中他们分别来自于单独编译的库
  6. 此外,我们尽可能的把共享的部分集中在一起,这样就需要一个副本,改进程序时也容易保证程序的正确性。我们把这些公共部分放在头文件calc.h中。

4.6静态变量

static 使外部无法访问

4.7寄存器变量

  • register声明: 它所声明的变量在程序中使用频率变高
  • register声明只适用于自动变量以及函数的形式参数,且无论寄存器变量是不是放在寄存器中,它的地址都是无法访问的,且不同机器中对寄存器变量的数量和类型的具体限制也是不同的。
register int x;
register char c;

4.8程序块结构

  • C语言不允许在函数中定义函数,但是在函数中可以以程序块结构的形式定义变量。

  • 变量的声明(包括初始化)除了可以紧跟在函数开始的花括号之后,还可以紧跟在任何其他标识复合语句开始的左花括号之后。以这种方式声明的变量可以隐藏程序块外与之同名的变量,它们之间没有任何关系,并在与左花括号匹配的右花括号出现之前一直存在。

  • 每次进入程序块时,在程序块内声明以及初始化的自动变量都将被初始化。静态变量只在第一次进入程序块时被初始化一次。

  • 自动变量(包括形式参数)也可以隐藏同名的外部变量与函数。

  • 在一个好的程序设计风格中,应该避免出现变量名隐藏外部作用域中相同名字的情况,否则,很可能引起混乱和错误。

4.9初始化

  • 在不进行显示初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量的初值则没有定义(即初值为无用的信息)。

  • 定义标量变量时,可以在变量名后紧跟一个等号和表达式来初始化变量。

  • 对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只初始化一次。对于自动变量与寄存器变量,则在每次进入函数或程序块时都被初始化。

  • 对于自动变量与寄存器变量来说,初始化表达式可以不是常量表达式:表达式中可以包含任意在此表达式之前已经定义的值,包括函数调用。

  • 实际上,自动变量的初始化等效于简写的赋值语句。采用哪一种形式取决于个人习惯。考虑到变量声明中的初始化表达式容易被人忽略,且距使用位置较远,我们一般使用显式的赋值语句。

  • 数组的初始化可以在声明的后面紧跟一个初始化表达式列表,初始化表达式列表用花括号括起来,各初始化表达式之间通过逗号分隔,当省略数组的长度时,编译器把花括号中初始化表达式的个数作为数组的长度。

  • 如果初始化表达式的个数比数组元素数少,则对外部变量、静态变量和自动变量来说,没有初始化表达式的元素将被初始化为0。如果初始化表达式的个数比数组元素数多,则是错误的。不能一次将一个初始化表达式指定给多个数组元素,也不能跳过前面的数组元素而直接初始化后面的数组元素。

  • 字符数组的初始化比较特殊:可以用一个字符串来代替用花括号括起来并用逗号分隔的初始化表达式序列。

4.10递归

  • C语言函数可以递归调用,即函数可以直接或间接调用自身。

  • 递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。

  • 递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解。

4.8程序块结构

C语言不允许在函数中定义函数,但是在函数中可以以程序块结构的形式定义变量。

变量的声明(包括初始化)除了可以紧跟在函数开始的花括号之后,还可以紧跟在任何其他标识复合语句开始的左花括号之后。以这种方式声明的变量可以隐藏程序块外与之同名的变量,它们之间没有任何关系,并在与左花括号匹配的右花括号出现之前一直存在。

每次进入程序块时,在程序块内声明以及初始化的自动变量都将被初始化。静态变量只在第一次进入程序块时被初始化一次。

自动变量(包括形式参数)也可以隐藏同名的外部变量与函数。

在一个好的程序设计风格中,应该避免出现变量名隐藏外部作用域中相同名字的情况,否则,很可能引起混乱和错误。

4.9初始化

在不进行显示初始化的情况下,外部变量和静态变量都将被初始化为0,而自动变量和寄存器变量的初值则没有定义(即初值为无用的信息)。

定义标量变量时,可以在变量名后紧跟一个等号和表达式来初始化变量。

对于外部变量与静态变量来说,初始化表达式必须是常量表达式,且只初始化一次。对于自动变量与寄存器变量,则在每次进入函数或程序块时都被初始化。

对于自动变量与寄存器变量来说,初始化表达式可以不是常量表达式:表达式中可以包含任意在此表达式之前已经定义的值,包括函数调用。

实际上,自动变量的初始化等效于简写的赋值语句。采用哪一种形式取决于个人习惯。考虑到变量声明中的初始化表达式容易被人忽略,且距使用位置较远,我们一般使用显式的赋值语句。

数组的初始化可以在声明的后面紧跟一个初始化表达式列表,初始化表达式列表用花括号括起来,各初始化表达式之间通过逗号分隔,当省略数组的长度时,编译器把花括号中初始化表达式的个数作为数组的长度。

如果初始化表达式的个数比数组元素数少,则对外部变量、静态变量和自动变量来说,没有初始化表达式的元素将被初始化为0。如果初始化表达式的个数比数组元素数多,则是错误的。不能一次将一个初始化表达式指定给多个数组元素,也不能跳过前面的数组元素而直接初始化后面的数组元素。

字符数组的初始化比较特殊:可以用一个字符串来代替用花括号括起来并用逗号分隔的初始化表达式序列。

4.10递归

C语言函数可以递归调用,即函数可以直接或间接调用自身。

递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。

递归的执行速度并不快,但递归代码比较紧凑,并且比相应的非递归代码更易于编写与理解。

4.11C预处理器

  • 预处理器是编译过程中单独执行的第一个步骤。

  • 两个常用的预处理器指令是:#include指令(用于在编译期间把指定文件的内容包含进当前文件中)和#define指令(用任意字符序列替代一个标记)

  • 在源文件中,任何形如#include “文件名” 或#include <文件名> 的行都将被替换为由文件名指定的文件的内容。如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用尖括号括起来的,则根据相应的规则查找该文件。被包含的文件本身也课包含#include指令。

  • 在大的程序中,#include指令是将所有声明捆绑在一起的较好的方法。它保证所有源文件都具有相同的定义与变量声明,可以避免一些不必要的错误。如果某个包含文件的内容发生了变化,那么所有依赖于该包含文件的源文件都必须重新编译。

4.112宏编译

定义的形式如下:#define 名字 替换文本 ,后续所有出现名字记号的地方都将被替换为替换文本。

#define指令中的名字与变量名的命名方式相同,替换文本可以是任意字符串。

通常情况下#define指令占一行,替换文本是#define指令行尾部的所有剩余部分内容,但也可以把一个较长的宏定义分成若干行,这时需要在待续的行末尾加上一个反斜杠号\。

#define指令定义的名字的作用域从其定义点开始,到被编译的源文件的末尾处结束。

宏定义中也可以使用前面出现的宏定义,替换只对记号进行,对括在引号中的字符串不起作用。

宏定义也可以带参数,这样可以对不同的宏调用使用不同的替换文本。

如果对各种类型的参数的处理是一致的,则可以将同一个宏定义应用于任何数据类型而无需针对不同的数据类型需要定义不同的函数。

对于带参数的宏定义,要适当使用圆括号以保证计算次序的正确性。

getchar与putchar函数在实际中常常被定义为宏,这样可以避免处理字符时调用函数所需的运行时开销。

可以通过#undef指令取消名字的宏定义,这样可以保证后续的调用是函数调用而不是宏调用。

形式参数不能用带引号的字符串替换。

如果在替换文本中,参数名以#作为前缀则结果将被扩展为由实际参数替换该参数的带引号的字符串。例如,#define dprint(expr) printf(#expr" = %g\n",expr) ,使用语句dprint(x/y); 调用该宏时,将被扩展为printf(“x/y” " = %g\n",x/y); 该宏调用的效果等价于printf(“x/y = %g\n”,x/y); 在实际参数中,每个双引号将被替换为",反斜杠将被替换为\,因此替换后的字符串是合法的字符串常量。

预处理器运算符##为宏扩展提供了一种连接实际参数的手段,如果替换文本中的参数与##相邻,则该参数将被实际参数替换,##与前后的空白符将被删除,并对替换后的结果重新扫描。例如#define paste(front,back) front##back ,宏调用paste(name,1)的结果将建立记号name1。

4.113条件包含

可以使用条件语句对预处理本身进行控制。

#if语句对其中的常量整型表达式(不能包含sizeof、类型转换运算符或enum常量)进行求值,若该表达式的值不等于0,则包含其后的各行,直到遇到#endif、#elif或#else语句为止,预处理器语句#elif类似于 else if。

在#if语句中可以使用表达式defined(名字),该表达式的值遵循下列规则:当名字已经定义时,其值为1;否则,其值为0。例如,为了保证hdr.h文件的内容只被包含一次,可以将该文件包含的内容包含在下列形式的条件语句中:

#if !defined(HDR)
#define HDR
/*文件内容*/
#endif

这样可以用来避免多次重复包含同一文件,如果多个头文件能够一致地使用这种方式,那么每个头文件都可以将它所依赖的任何头文件包含进来,用户不必考虑和处理头文件之间的各种依赖关系。

C语言专门定义了两个预处理语句#ifdef和#ifndef,它们用来测试某个名字是否已经定义。上面一个例子可以改写为:

#ifndef HDR
#define HDR
/*文件内容*/
#endif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

遗落-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值