《C语言程序设计》琐碎知识点总结笔记

《C语言程序设计》琐碎知识点总结笔记

本篇笔记旨在拾掇干净一些琐碎的小/冷知识点,免得给之后的学习留不必要的念想。

一、变量的存储类别

变量的定义形式:

[存储类别] 类型标识符 变量名

现对“存储类别”加以说明。

存储空间分为程序区数据区数据区分为静态存储区动态存储区

存储类别有:自动型(auto)、寄存器型(register)、静态型(static)、外部型(extern)。

1.局部变量的存储类别

局部变量的存储类别有:自动型、静态型、寄存器型。

(1)自动型局部变量

动态存储区分配存储空间,调用函数时分配存储空间,调用结束后自动释放存储空间。

默认的存储类别,相当于最常使用的、定义时不加“存储类别”的变量。

auto float m;

auto int a,b,c=10;

(2)静态型局部变量

静态存储区分配存储空间,调用函数时分配存储空间,调用结束后不会释放存储空间,并保留变量的值

人话:只进行第一次初始化,之后再遇到初始化语句则无视,但是可以进行赋值或自加自减,并且值会被记录。

int i;
for(i=1;i<=5;i++)
{
	static int c=3;
	c++;
	printf("%d ",c);	
}

输出:4 5 6 7 8

(3)寄存器型局部变量

将局部变量的值放在CPU通用寄存器中。

只有自动型局部变量和形式参数可以说明为寄存器型变量。(说明寄存器型满足自动型的特性,如可改变值)

register int a,b;

ps:曾见过有人将循环语句中的 \(i\) 声明为寄存器型来进行常数优化,但我的C语言老师又说寄存器型已经“名存实亡”,由此可见还是不要随便用寄存器型为好。

2.全局变量的存储类别

通过引用声明可以扩展全局变量的作用域(类似于函数的引用说明),引用声明的形式为:

extern 数据类型 变量名

全局变量的存储类别有:静态型、外部型。

(1)静态型全局变量

静态型全局变量只能被它所在文件中的函数使用,不能被其他文件中的函数使用。

可以通过引用声明本文件内扩展其作用域。

举例如下:

extern int a,b;// a,b的引用说明语句
int fun()
{
	if(a>b) return a;
	else return b;
}
static int a,b;// a,b的定义语句

上述代码也可直接写成:

static int a,b;
int fun()
{
	if(a>b) return a;
	else return b;
}

(2)外部型全局变量

若没有给出存储类别(即前面什么也不加),则定义的变量为外部型全局变量。

通过引用声明,可以将全局变量作用域扩展到定义它之前的函数,也可以扩展到程序中的其他文件

举例如下:

(源文件1)

int a,b;// 定义外部型全局变量a,b

(源文件2)

extern int a,b;// 外部型全局变量引用说明

二、函数的存储类别

函数的定义形式:

[函数存储类别][函数返回值类型]函数名([函数形式参数表])
{
	函数体说明部分
	函数功能语句序列
	[return 表达式;]
}

现对“函数存储类别”加以说明。

根据函数能否被其他源文件的函数调用,函数可以分为内部函数(static)和外部函数(extern)。

1.内部函数

存储类别为static,又称静态函数。

只能被它所在文件中的函数调用。

不同文件中的内部函数可以同名,互不干扰。

static int fun(int a,int b)
{
	return (a>b?a:b);
}

2.外部函数

存储类别为extern或省略存储类别

既能被它所在文件中的函数调用,也能被其他文件中的函数调用。

如果一个文件要调用另一个文件中定义的外部函数,则在调用函数的文件中一定要对被调用的外部函数进行引用声明

引用声明的形式:

extern 函数名([函数形式参数表])

举例如下:

(源文件1)

#include<stdio.h>
int main()
{
    extern fun(int a,int b);
	int a,b;
    scanf("%d%d",&a,&b);
	printf("%d\n",fun(a,b));
	return 0;
}

(源文件2)

#include<stdio.h>
int fun(int a,int b)
{
	return (a>b?a:b);
}

三、编译预处理

在编译之前进行的处理,以“#”开头。

1.宏定义

宏定义的本质是在程序编译前进行替换。

注意替换后的运算优先级,在必要时使用括号。

(1)不带参数的宏定义

定义与取消定义:

#define 宏名 宏体
#undef 宏名

举例如下:

#define PI 3.1415926

(2)带参数的宏定义

定义与取消定义:

#define 宏名(形参列表) 宏体
#undef 宏名

使用:

宏名(实参列表)

举例如下:

#define s(a,b) a>b?a:b

s(3,4)

宏替换为:3>4?3:4

2.文件包含

#include "文件名"

#include<文件名>

3.条件编译

形式1:

#ifdef 标识符
	程序段1
[#else
	程序段2]
#endif

若标识符已经被定义(使用#define 标识符),则对程序段1进行编译,否则对程序段2进行编译。

形式2:

#ifndef 标识符
	程序段1
[#else
	程序段2]
#endif

若标识符未被定义,则对程序段1进行编译,否则对程序段2进行编译。

形式3:

#if 常量表达式
	程序段1
[#else
	程序段2]
#endif

若表达式的值为真,则对程序段1进行编译,否则对程序段2进行编译。

四、主函数与命令行参数

主函数的格式为:

void main([int argc,char *argv[]])
{
	...
}

()中的信息称为命令行参数。

argc用于保存用户命令行中输入的命令中参数的个数,命令名本身也是一个参数。

argv[]是一个字符指针数组,用于保存各个参数的首地址(包括命令名本身)。

五、变量的作用域

ps:只提几个关键点。

1.{}括起来的是一个复合语句,其中定义的局部变量只能在该复合语句中使用。

2.主函数中定义的变量也是局部变量,只在主函数中有效。

3.不同函数中定义的局部变量可以同名,它们互不干扰。

4.形式参数也是局部变量,只在它所在的函数中有效。

5.全局变量的作用域从定义点开始直到文件尾,可以被作用域内的所有函数共用。

6.在同一个源文件内,如果全局变量和局部变量同名,则在局部变量的作用域内全局变量不起作用。(强权难压地头蛇)

六、运算符与表达式

1.算术运算符和算术表达式

(1)算术运算符

双目:+加法 -减法 *乘法 /除法 %取模

单目:+取正 -取负 ++自加 --自减

取负不改变变量的值。

自加自减运算符只能用于变量,不能用于常量和表达式。

(2)算术表达式

单个的常量和变量都是算术表达式。

算术表达式的值是数值型。

(3)优先级

高:-(取负)、++、--

中:*、/、%

低:+、-

(4)结合方向

右结合:-(取负)、++、--

左结合:+、-、*、/、%

当++、--、+、-混合运算时,应自左向右尽可能地组合运算符。

(5)类型转换

自动类型转换:从低精度类型转换成高精度类型。

精度从高到低:double->long->unsigned->int->char

强制类型转换

(类型标识符) (表达式)

2.关系运算符和关系表达式

(1)关系运算符

双目:> >= < <= == !=

(2)关系表达式

关系表达式的值只能是1或0,数据类型为整型,代表关系运算结果的真/假。

(3)优先级

高:>、>=、<、<=

低:==、!=

(4)结合方向

均为左结合

3.逻辑运算符和逻辑表达式

(1)逻辑运算符

双目:&&逻辑与 ||逻辑或

单目:!逻辑非

(2)逻辑表达式

逻辑表达式的值:1代表真,0代表假。

若a,b都为真,则a&&b为真,否则为假;

若a,b都为假,则a||b为假,否则为真;

若a为真,则!a为假;若a为假,则!a为真。

(3)优先级

高:!

中:&&

低:||

(4)结合方向

&& ||为左结合,!为右结合。

并不是所有逻辑运算都被执行,只有在必须执行下一个逻辑运算才能求出表达式的值时,才执行该运算。

4.赋值运算符和赋值表达式

(1)赋值表达式

赋值表达式的值就是被赋值的变量的值,赋值表达式的值的类型就是被赋值的变量的类型。

(2)结合方向

赋值运算符的结合方向都是右结合。

a=b=c=10等价于a=(b=(c=10))

赋值允许连等,但定义变量时不允许,错误写法:

int a=b=c=10;

赋值表达式中的赋值运算符的左侧必须是变量,不能是常量和表达式。

由此引申出一个易错点:C语言中给字符数组赋值的问题。

现有字符数组(用于实现字符串):

char str[100];

错误的赋值方式:

str="yuhaoxuan";

str[100]="yuhaoxuan";

正确的赋值方式:

char str[]="yuhaoxuan";(对部分元素初始化,末尾自动添'\0')

char str[100]="yuhaoxuan";(对全部元素初始化,数组元素数量不足时末尾无'\0')

以上两种赋值方式等号右边也可写成{"yuhaoxuan"}{'y','u','h','a','o','x','u','a','n','\0'}

strcpy(str,"yuhaoxuan");(使用字符串赋值函数)

5.逗号运算符和逗号表达式

形式:

表达式1,表达式2[,表达式3,...,表达式n]

求解过程:从左到右依次计算每个表达式的值。

逗号表达式的值就是“表达式n”的值,逗号表达式的值的类型就是“表达式n”的值的类型。

6.条件运算符和条件表达式

形式:

表达式1?表达式2:表达式3

表达式1一般为关系表达式或逻辑表达式,表达式2和3同类型。

求解过程:执行表达式1,若表达式1的值非0,则执行表达式2,表达式2的值就是条件表达式的值;若表达式1的值为0,则执行表达式3,表达式3的值就是条件表达式的值

七、switch~case语句

这玩意用得少,时常会忘,有必要总结一下。

并且在输出枚举型变量时还必须用到它。

一般形式

switch(表达式)
{
	case 常量表达式1: 语句1 [break;]
	case 常量表达式2: 语句2 [break;]
	...
	case 常量表达式n: 语句n [break;]
	[default:语句n+1 [break;]]
}

执行过程

① 计算switch后面圆括号中的表达式的值;

② 用其结果依次与各case后面的常量表达式的值进行比较,若相等,则执行该case后面语句;执行时如果遇到break语句,就退出switch~case语句,转至花括号的下方否则顺序往下执行;

③ 若与各case后面的常量表达式的值都不相等,则执行default后面的语句。

要求说明

(就是因为要求太多这玩意才不常用)

  • switch后面的表达式和各case后面的表达式的类型必须为整型、字符型或枚举型;

  • 各case后面的常量表达式的的值必须互不相同

  • case后面的语句可为空语句,但default后面的语句不能为空语句;

  • 多个case可以共用一组语句;

  • default可以省略,当没有与表达式的值相匹配的常量表达式时,直接退出switch~case语句;

  • 执行完一个case后面的语句后,如果没有遇到break语句,则不再进行判断直接执行下一个case后面的语句;若想执行某一语句后退出,则必须在其后加上break语句;

  • 别忘了default如何拼写。

模板程序

#include<stdio.h>
int main()
{
	int a;
	char y;
	scanf("%d",&a);
	switch(a/10)
	{
		case 10:y='A';break;
		case 9:y='A';break;
		case 8:y='B';break;
		case 7:y='C';break;
		case 6:y='D';break;
		default:y='E';break;
	}
	printf("y=%c\n",y);
	return 0;
}

八、字符串末尾'\0'问题

1.初始化

  • 字符常量对字符数组全部元素初始化

char str[3]={'U','S','A'};

等价于

char str[]={'U','S','A'};

str 数组各元素的值:

U S A
  • 字符常量对字符数组部分元素初始化

char str[6]={'U','S','A'};

str 数组各元素的值:

U S A \0 \0 \0
  • 字符串常量对字符数组部分元素初始化

char str[]={"USA"};

等价于

char str[]="USA";

等价于

char str[]={'U','S','A','\0'};

str 数组各元素的值:

U S A \0

2.键盘输入

  • gets() 和 scanf("%s") 输入字符串时,都会自动在字符串后面添加'\0'.

九、定义变量时的细节

1.标识符

标识符用于标识常量名、变量名、函数名、数组名、类型名。

标识符取名规则:

(1) 只能由大小写英文字母(A ~ Z , a ~ z)、数字(0 ~ 9)、下划线(_)组成;

(2) 第一个字符不能为数字

2.连等

定义变量时不能用连等,给变量赋值时可以用连等

十、判断含转义字符的字符串长度

一个转义字符视作一个字符,长度记为1

(1) 固定形式的转义字符:\n \b \t \r \a \v \f \0 斜杠+单引号 斜杠+双引号 双斜杠

(2) 含不确定数字/字母的转义字符:\ddd (d可换成任意八进制数0~7) \xhh(x固定,h可换成任意十六进制数0~f/F)

eg.ab\\c\td\376长度为7(a b \\ c \t d \376

十一、运算顺序问题

括号、自加自减运算符、星号(解引用)同时出现时,需考虑运算顺序。

前置知识:

  • 自加自减运算符和星号优先级相同,都是右结合的单目运算符;
  • 对于一个含括号的完整式子,需先将所有括号内的内容运算完,再运算括号外的内容。(任何括号外的内容不得先于任何括号内的内容进行运算)

易错点1:自加自减运算符与括号

int i,k;
i=3;
k=(i++)+(++i)+(i++);
printf("i=%d k=%d\n",i,k);

等价于

i=i+1;(=4)

k=i+i+i;(=12)

i=i+1;(=5)

i=i+1;(=6)

输出:i=6 k=12

(不同编译环境输出结果可能有不同,以教材为准)

易错点2:自加自减运算符与星号

eg: *p++

p先与右边的++结合,即 *p++ 等价于 *(p++)

意思是移动指针。

更多eg:

int a[]={1,3,5};
int *p;

*(p++) <=> *p++(移动指针)

p=&a[1];
printf("%d %d",*(p++),*p)

输出 3 5

*(++p) (移动指针)

p=&a[1];
printf("%d %d",*(++p),*p)

输出 5 5

(*p)++(改变指向的值)

a[1]=3;
p=&a[1];
printf("%d %d",(*p)++,*p)

输出 3 4

++(*p)(改变指向的值)

a[1]=3;
p=&a[1];
printf("%d %d",++(*p),*p)

输出 4 4

自减同理。

十二、自定义类型(typedef)

作用

为已有类型重新命名,从而将一些复杂的类型名简单化。

格式

typedef 原类型名 新类型名;

例如:

typedef int INTEGER;
typedef float REAL;
typedef struct {int year,month,day;} DATE;	//给无名结构体类型起了个别名DATE
INTEGER	a;
REAL f;
DATE today;	//相当于 struct {int year,month,dat;} today;

使用步骤

(1):按定义变量的方法写出定义体,如

struct {int year,month,day;} today;

(2):将变量名换成新类型名,如

struct {int year,month,day;} DATE;

(3):在最前面加上typedef,如

typedef struct {int year,month,day;} DATE;

(4):用新的类型名定义变量,如

DATE yesterday,today,tomorrow;

自定义数组类型名ARRAY

写:int a[100];

换:int ARRAY[100];

加:typedef int ARRAY[100];

用:ARRAY a,b,c; //相当于int a[100],b[100],c[100];

自定义结构体类型名ST和结构体指针STU类型名

写:struct student stu,*p;

换:struct student ST,*STU;

加:typedef struct student ST,*STU;

用:ST s1,s2; STU p,q; //s1和s2是struct student类型的变量,p和q是指向struct student类型的指针

posted @ 2025-12-22 01:07  Fish4174  阅读(99)  评论(0)    收藏  举报