本章归纳C语言中疑难知识点,容易出错的用法及语法,特殊用法扩展等
目录
第一章 C语言概述
第二章 算法——程序的灵魂
第三章 数据的表现形式及其运算
3.1 数据类型及运算
3.1.1 常量和变量
3.1.2 数据类型
3.1.3 整型数据
3.1.4 字符型数据
<1>关于罕见字符的输出:‘空格’, ‘’,‘\0’
3.1.5 常量和变量浮点型数据
3.1.6 怎样确定常量的类型
3.1.7 运算符和表达式
1.基本算数运算符
2.自增、自减运算符
补充:i++与++i的区别
3.其他运算符
1)三目运算符: ? : 合起来叫做三目运算符
2)逗号表达式
3)强制类型转换
4)C语言中反斜杠的作用
5)标识符(变量、符号常量名、函数、数组、类型等命名的有效字符序列)
4.算数表达式和运算符的优先级与结合性
3.2 C语句
3.2.1 C语句分类
3.2.2 最基本的语句——赋值语句
第四章 顺序结构、选择结构程序设计
4.1 顺序结构程序设计
4.2 选择结构和条件判断
4.3 用 if 语句实现选择结构
<0>正确的条件判断表达式格式类型
<1> if{...} if{...} if{...} 语句 与 if{...} else if{...} else if{...} 语句的区别?
<2> if对真假逻辑的处理
<3>if(表达式)语句判断表达式的使用注意事项
4.4 关系运算符和关系表达式
4.5 逻辑运算符和逻辑表达式
4.6 条件运算符和条件表达式
4.7 选择结构的嵌套
4.8 用 switch 语句实现多分支选择结构
第五章 循环结构程序设计
第六章 利用数组处理批量数据
6.1 字符数组的规范初始化方式
6.2 数组元素的调用(注意:在norflash中的局部变量是无法存入的,只有Nandflash启动才可以使用局部变量)
6.3 数组/字符串元素的指针调用方法
第七章 用函数实现模块化程序设计
7.1 函数的定义
7.2 函数的调用
7.3 对被调函数的声明和函数原型
7.4 函数的嵌套使用
7.5 函数的递归使用
7.6 数组作为函数参数
7.7 局部变量和全局变量
7.8 变量的存储方式和生存期
7.9 变量的声明和定义
7.10 内部函数和外部函数
摘自:维基百科》static
7.11 C库函数常见函数的使用
1.printf()函数的运用
第八章 指针
8.1指针变量的类型与含义的归纳比较
8.2使用函数指针调用函数的正确方法:
本文所有内容,有摘自谭浩强《C程序设计》,百度百科,视频资料等等,所记所述为笔者平日代码编写犯过或经常困惑的知识。
int j = sizeof(p); //p为指针
char a = ' ';
char b = '\0';
//char b = ''; //其中,''内为空,运行结果:error!空字符常数!
char c = 'M';
printf("j = %d\n", j);
printf("a = %c, b = %c, c = %c\n", a, b, c);
/*
在vc++6.0输出结果是:
j = 4
a = , b = , c = M
字符'0' :char c = '0'; 它的ASCII码实际上是48。内存中存放表示:00110000
字符'\0' : ASCII码为0,表示一个字符串结束的标志。这是转义字符。
整数0 :ASCII码为0,字符表示为空字符,NULL;数值表示为0;内存中表示为:00000000
注意:当指针 p = NULL; 代表该指针变量不指向任何变量,其中,NULL是一个符号常量,代表整数0。在stdio.h头文件中对NULL进行了定义: #define NULL 0
郝斌C语言课程讲8:运算符补充,内容是:自增自减,三目运算符,逗号表达式的使用及各种运算符混合使用的优先级总结。
分类:前自增: ++i; 后自增:i++;
相同处:最终都使i的值加1;
不同处:前自增整体表达式的值是 i 加 1 之后的值,后自增整体表达式的值是 i 加 1 之前的值。
#include
int main(void)
{
int i, j, k, m;
i = j = 3;
k = i++;
m = ++j;
printf("i = %d, j = %d, k = %d, m = %d\n", i, j, k, m);
ruturn 0;
}
在vc++6.0中的输出结果为: i = 4, j = 4, k = 3, m = 4
问题:为什么会出现自增?
答:1)代码更精炼 2)自增的速度更快
解析:i= i+ 1;⇔(等价于)i+=1;内部的执行一模一样 /****/ i= i+ 1;≠(不等价于)i++;
学习自增时需要明白的问题:
答:1)编程时,应该尽量屏蔽掉前自增和后自增的差别 2)自增表达式最好不要作为一个更大的表达式的一部分来使用,或者说,i++和++i单独成一个语句,不要把它作为一个完整的复合语句的一部分来使用。
例:int m = i++ + ++i + i + i++; //这样写不但是不规范的代码,而且是不可移植的代码(该代码在不同的机器的运行结果不一样)
原因:执行(++i)时不知道(i++) 中 i 是否加1,是否立即生效,只有明晰顺序点,才能了解其是否必须在下一语句前已生效
延伸:顺序点(只有三种):逗号; 括号(); 分号;
链接:https://www.zhihu.com/question/19811087/answer/80210083
i++ 与 ++i 的主要区别有两个:
1、 i++ 返回原来的值,++i 返回加1后的值。
2、 i++ 不能作为左值,而++i 可以。
毫无疑问大家都知道第一点(不清楚的看下下面的实现代码就了然了),我们重点说下第二点。
首先解释下什么是左值(以下两段引用自中文维基百科『右值引用』词条)。
左值是对应内存中有确定存储地址的对象的表达式的值,而右值是所有不是左值的表达式的值。
一般来说,左值是可以放到赋值符号左边的变量。但能否被赋值不是区分左值与右值的依据。比如,C++的const左值是不可赋值的;而作为临时对象的右值可能允许被赋值。左值与右值的根本区别在于是否允许取地址&运算符获得对应的内存地址。
比如,
int i = 0;
int *p1 = &(++i); //正确
int *p2 = &(i++); //错误
++i = 1; //正确
i++ = 5; //错误
那么为什么『i++ 不能作为左值,而++i 可以』?
看它们各自的实现就一目了然了:
以下代码来自博客:为什么(i++)不能做左值,而(++i)可以
// 前缀形式:
int& int::operator++() //这里返回的是一个引用形式,就是说函数返回值也可以作为一个左值使用
{//函数本身无参,意味着是在自身空间内增加1的
*this += 1; // 增加
return *this; // 取回值
}
//后缀形式:
const int int::operator++(int) //函数返回值是一个非左值型的,与前缀形式的差别所在。
{//函数带参,说明有另外的空间开辟
int oldValue = *this; // 取回值
++(*this); // 增加
return oldValue; // 返回被取回的值
}
如上所示,i++ 最后返回的是一个临时变量,而临时变量是右值。
格式: A ? B : C ⇔(等价于)
if(A)
B;
else
C;
例: int i = (3 > 2 ? 5 : 1); ➩得:i = 5;
格式:(A, B, C, D)
功能:从左到右执行,最终表达式的值是最后一项的值
例1:int i = (3, 2, 5, 6); ➩得: i = 6;
例2:int i, j = 2; i = (j++, ++j, j+2, j-3); ➩得:i = 1 为什么?
/*
2018--05-20
功能:
验证逗号表达式和自增运算符的使用
*/
#include
int main(void)
{
int i, j = 2;
i = (j++, ++j, j+2, j-3);
printf("i = %d\n", i);
return 0;
}
/*
在vc++6.0中的输出结果为:
i = 1
Press any key to continue
解析:1.j++ → j = 3
2.++j → j = 4
3.j+2 → j = 4(虽然表达式的值加2,但j本身并无增加)
4.j-3 → j = 1
5.i = j-3 = 1;
*/
强制类型转换表达式:(type)表达式
参考:https://baike.baidu.com/item/C%E8%AF%AD%E8%A8%80%E8%BF%90%E7%AE%97%E7%AC%A6/1539281?fr=aladdin#1_3
3.1 C语言强制类型转换
强制类型转换是通过类型转换运算来实现的。其一般形式为: (类型说明符) (表达式) 其功能是把表达式的运算结果强制转换成类型说明符所表示的类型。
1>强制转换变量的数据类型 2>强制转换数据的数据类型
例如: (float) a 把变量a转换为浮点型,(int)(x+y) 把x+y的结果转换为整型。
3.2 注意事项:
1>类型说明符和表达式都必须加括号(单个变量可以不加括号),如把(int)(x+y)写成(int)x+y则成了把x转换成int型之后再与y相加了。
2>无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。
详细参考:维基百科https://baike.baidu.com/item/C语言类型强制转换/7593367
3.3 C语言与C++的不同类型转换的问题
代码:
temp=(float)sum/total;
ave=((int)(temp+0.5)==(int)temp) ? (int)temp:int(temp+0.5);
编译结果:
//error: syntax error before "int",
error: syntax error before "int",
C++编译器没问题,但C语言的编译器就会出现error,主要是强制类型转换的格式不一样。
比如将int b强制类型转换为double类型,那么C语言必须:
int b = 5;
double a = (double)b;
C++是偏向面向对象的,可以用:
a = double(b);
转载链接:https://blog.csdn.net/shanshangyouzhiyangM/article/details/52931920
反斜杠起到换行作用,用于宏定义和字符串换行。其中宏定义中使用居多。
如果一行代码有很多元素,导致太长影响阅读,可以通过在结尾加\的方式,实现换行,编译时会忽略\及其后的换行符,当做一行处理。
1>在宏定义中,要换行必须使用 \ 结尾。
#define CHECK_ACTION_RETURN(expr) \
if (!expr) { \
printf(":failed(%d)\n", ret); \
return ret; \
} else { \
printf(":ok\n"); \
}
2>在字符串常量中,可以使用 \ 结尾。
"this \
is \
for \
testing"
和”this is for testing”是相同的,但是对于字符串写成
"this "
"is "
"for "
"testing"
效果是相同的,而且更美观。
3>另外,在普通的语句中,也可以通过 \ 实现换行,不过这时没有 \ 也是一样的效果。
比如
printf("this is for test %d %d %d\n",\
test_output_a,\
test_output_b,\
test_output_c);
和
printf("this is for test %d %d %d\n",
test_output_a,
test_output_b,
test_output_c);
是没有区别的,所以这时一般不会使用 \ 。
在计算机高级语言中,用来对变量、符号常量名、函数、数组、类型等命名的有效字符序列统称为标识符。简单来说,标识符就是一个对象的名字。例如:变量名a, b, str,符号常量名GPFCON, GPFDAT, 函数名printf等都是标识符。
C语言规定标识符只能是由字母、数字和下划线3种字符组成,且第1个字符必须为字母或者下划线。
注意:编译系统将大小写字母认为是两个不同的字符。一般而言,变量名用小写字母表示,与人们日常习惯一致,以增加可读性。
<1>符号常量 —— #define 宏定义的疑难用法
符号常量不同于常变量,定义符号常量用#define指令,是预编译指令,只是用符号常量代表一个字符串,仅在预编译时进行字符替换,不分配存储单元,预编译之后,符号常量就不存在了。而常变量是占用存储单元的,本质是变量,仅值不变而已。
对于符号常量#define GPFCON (*(volatile unsigned int *)0x56000050),则&GPFCON = 56000050且有效!
#include
#include
char * str = "12345678";
char str2[] = "abcdef";
#define GPFCON (*(volatile unsigned int *)0x56000050)
#define GPFDAT (*(volatile unsigned int *)0x56000054)
int main(void)
{
int i = 0;
int a = 1;
int b = 2;
int c = 3;
if(a == 1)
printf("aaa\n");
else if(b == 2)
printf("bbb\n");
else if(c == 3)
printf("ccc\n");
printf("===================================\n");
for(i = 0; i < 6; i++)
printf("*str = %c\n", *str++);
// for(i = 0; i < 6; i++)
// printf("*str2 = %c\n", *str2++);
printf("===================================\n");
printf("&GPFCON = %x\n", &GPFCON);
printf("&GPFDAT = %x\n", &GPFDAT);
return 0;
}
/*
打印结果:
aaa
===================================
*str = 1
*str = 2
*str = 3
*str = 4
*str = 5
*str = 6
===================================
&GPFCON = 56000050
&GPFDAT = 56000054
Press any key to continue
*/
如上图各运算符优先级顺序。
本课程的上机总结规律为: (!) > (+, -, *, /, %) > (>, <, >=, <=, ==) > (&&, ||)
例如: for(i = 0; 表达式a; i++); while(表达式b); if(表达式c);
C/C++语言表达式格式为任意格式:(按优先级排序)算数表达式>关系表达式>逻辑表达式>赋值表达式
答:if{...} if{...} if{...} 是进行三次判断,且执行三次操作;
if{...} else if{...} else if{...}执行1~3次判断,但只执行一次操作; 使用的境况有区别,请区分使用!
#include
int main(void)
{
int a = 1;
int b = 2;
int c = 3;
if(a == 1)
printf("aaa\n");
else if(b == 2)
printf("bbb\n");
else if(c == 3)
printf("ccc\n");
printf("===================================\n");
return 0;
}
在vc++6.0打印结果:
aaa
===================================
Press any key to continue
小结:if{...} if{...} if{...} 是三个语句; 而 if{...} else if{...} else if{...}归根到底是一个语句!
if(a): a非0为真,a=0为假
常见的错误写法:
a)i = 0; while(!(i &= (1<<2))){i ++}; 则:while()内语句始终为真,进入while()语句循环
b)i = 4; while(!(i &= (1<<2))){i ++}; 则:while()内语句为假,不循环,直接跳过while()语句
规范写法:
while(!(i & (1<<2))),则当i = 4时,跳出while()循环。
#include
int main(void)
{
volatile int i = 0;
int j = 0;
while(!(i &= (1<<2))) //实际被判断的是变量i的值
{
printf("第一循环! ");
printf(" %#x\n", i);
if(j > 10)
break;
i++;
j++;
}
printf("\n");
i = 0x0;
j = 0;
while(!(i & (1<<2))) //实际被判断的是变量i的第三位的值
{
printf("第二循环! ");
printf(" %#x\n", i);
if(j > 10)
break;
i++;
j++;
}
printf("\n");
printf("我靠!\n");
return 0;
}
/*
实验结果:
(当int i bit2不为1时)
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
第一循环!0
@循环陷入无限死循环:
@ i = (0 & 4) = 0;
@ i = i+1 = 1;
@ i = (1 & 4) = 0;
@ i = i + 1 = 1;
@ ...
(当int i bit2为1时)
(空)
第二循环!0
第二循环!0x1
第二循环!0x2
第二循环!0x3
我靠!
Press any key to continue
总结:
(1)循环while(!(i &= (1<<2)))会改变变量i的值,把除bit2外其它位清零,如果初值i的bit2=1,则直接跳过循环;否则陷入死循环
(2)循环while(!(i & (1<<2)))不改变变量i的值
*/
在ARM硬件置位有先后之分的,一般不要在判断表达式中添加运算等。
赋值表达式? :=, +=, -=, *=, /=(一般不要在判断表达式中添加诸如赋值、算数运算之类的操作,增加了程序的复杂度和风险)。
在C语言中,是将字符串作为字符数组来处理的。如:char c[11] = {'I', ' ', 'L', 'O', 'V', 'E', ' ', 'Y', 'O', 'U', '!' };即是用一个一维的字符数组来存放字符串“I LOVE YOU!”的,字符串中的字符是逐个存放到数组元素中的。本例中,字符串实际长度和数组长度相等。
实际工作中,人们更关心字符串的有效长度而非字符数组的长度。C语言规定,以字符 '\0' 作为“字符串结束标志”。
字符数组初始化方法:
1)初始化列表,把各个元素依次赋值给数组中的各元素。如:char c[11] = {'I', ' ', 'L', 'O', 'V', 'E', ' ', 'Y', 'O', 'U', '!' };
长度11字节;
2)自定义。 char c[ ] = {'I', ' ', 'L', 'O', 'V', 'E', ' ', 'Y', 'O', 'U', '!' };
3)用字符串常量初始化字符数组,例:char c[ ] = {"I LOVE YOU!"}; 或 char c[ ] = "I LOVE YOU!";
注意:此法数组c的长度为12字节,末尾系统自动加 '\0' 空字符。
说明:字符数组并不要求他的最后一个字符为 '\0' ,甚至可以不包含'\0' ,合法。但为求处理方法一致,应人工于字符数组末人为加上'\0' 空字符。
正确字符串初始化方式:
char * a = "0123456789abcdef\n";
char * a = "Hello world!\n";
错误字符串初始化方式:
int a = "Hello world!\n";
26行代码可被识别:
31行代码可被识别:
31行代码可被识别:
1)数组的数组名是一个常量,是一个地址/指针,而非指针变量;
测试平台:ubuntu;
编 译 器:gcc
volatile unsigned char str[100];
*str++; ...
答:str是常量!!!如此使用是非法的,会造成编译错误!!
2)字符串指针不是常量,可以进行指针算数运算;
平台:vc++6.0;
#include
#include
char * str = "12345678";
int main(void)
{
int i = 0;
int a = 1;
int b = 2;
int c = 3;
if(a == 1)
printf("aaa\n");
else if(b == 2)
printf("bbb\n");
else if(c == 3)
printf("ccc\n");
printf("===================================\n");
for(i = 0; i < 6; i++)
printf("*str = %c\n", *str++);
return 0;
}
/*
打印结果:
aaa
===================================
*str = 1
*str = 2
*str = 3
*str = 4
*str = 5
*str = 6
Press any key to continue
*/
6.4 杂项
<1>符号常量做数组长度:
#define NUM 7
int a[NUM] = {1, 2, 3, 4, 5, 6, 7};
int main(void)
{
printf("中位数: a[NUM/2] = %d\n", a[NUM/2]);
return 0;
}
/*在VC++6.0中的运行结果:
中位数: a[NUM/2] = 4
Press any key to continue
*/
函数分为内部函数和外部函数
当一个源程序由多个源文件组成时,C语言根据函数能否被其它源文件中的函数调用,将函数分为内部函数和外部函数。
内部函数(又称静态函数)
如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其它文件中的函数调用,这种函数称为内部函数。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static 函数类型 函数名(函数参数表){……}
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
外部函数
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern] 函数类型 函数名(函数参数表){……}
调用外部函数时,需要对其进行说明:
[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
[案例]外部函数应用。
⑴文件mainf.c
main()
{
extern void input(…),process(…),output(…);
input(…);
process(…);
output(…);
}
⑵文件subf1.c
……extern void input(……) /*定义外部函数*/{……}
⑶文件subf2.c
……extern void process(……) /*定义外部 函数*/{……}
⑷文件subf3.c
……extern void output(……) /*定义外部函数*/{……}静态局部变量static的存储 有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存),而且降低了程序的可读性,因此若非必要,不要多用静态局部变量。vb中语句
在过程级别中使用,用于声明变量并分配存储空间。在整个代码运行期间都能保留使用 Static 语句声明的变量的值。
static语句声明的变量,与dim语句声明的变量的主要区别是:前者只能在sub或function过程中使用,在退出sub或function过程后变量的值保留;后者使用在sub或function过程中时,退出sub或function过程后变量的值不保留。
语法Staticvarname[([subscripts])] [As [New]type] [,varname[([subscripts])] [As [New]type]] . . .
Static 语句的语法包含下面部分:
描述
varname 必需的。变量的名称;遵循标准变量命名约定。
subscripts 可选的。数组变量的维数;最多可以定义 60 维的多维数组。subscripts 参数使用下面的语法:
[lower To] upper [,[lower To] upper] . . .
如果不显式指定 lower,则数组的下界由 Option Base 语句控制。如果没有 Option Base 语句则下界为 0。
New 可选的。用它可以隐式地创建对象的关键字。如果使用 New 声明对象变量,则在第一次引用该变量时将新建该对象的实例,因此不必使用 Set 语句来对该对象引用赋值。New 关键字不能用来声明任何内部数据类型的变量,也不能用来声明从属对象的实例。
type 可选的。变量的数据类型;可以是
Byte、Boolean、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、String(对变长的字符串)、String * length(对定长的字符串)、Object、Variant、用户定义类型或对象类型。
所声明的每个变量都要有一个单独的 As type 子句。
说明
模块的代码开始运行后,使用 Static 语句声明的变量会一直保持其值,直至该模块复位或重新启动。可以在非静态的过程中使用 Static 语句显式声明只在该过程内可见,但具有与包含该过程定义的模块相同生命期的变量。
可以在过程中使用 Static 语句来声明在过程调用之间仍能保持其值的变量的数据类型。例如,下面的语句声明了一个定长的整型数组:
Static EmployeeNumber(200) As Integer
下面的语句为 worksheet 的新实例声明了一个变量:
Static X As New Worksheet
如果在定义对象变量时没有使用 New 关键字,则在使用该变量之前,必须使用 Set 语句将一个已有的对象赋给这个引用对象的变量。在被赋值之前,所声明的这个对象变量有一个特定值 Nothing,这个值表示该变量没有指向任何对象的实例。若在声明中使用了 New 关键字,则在第一次引用对象时将新建一个该对象的实例。
如果不指定数据类型或对象类型,且在模块中没有使用 Deftype 语句,则按缺省情况,定义该变量为 Variant 类型。
注意
Static 语句与 Static 关键字很相似,但是针对不同的效果来使用的。如果使用 Static 关键字(如 Static Sub CountSales ())来声明一个过程,则该过程中的所有局部变量的存储空间都只分配一次,且这些变量的值在整个程序运行期间都存在。对非静态过程而言,该过程每次被调用时都要为其变量分配存储空间,当该过程结束时都要释放其变量的存储空间。Static 语句则用来在非静态的过程中声明特定的变量,以使其在程序运行期间能保持其值。
在初始化变量时,数值变量被初始化为 0,变长的字符串被初始化为一个零长度的字符串 (""),而定长的字符串则用 0 填充。Variant 变量被初始化为 Empty。用户自定义类型的变量的每个元素作为各自独立的变量进行初始化。
注意 如果在过程中使用 Static 语句,应和其它的声明语句(如 Dim)一样将其放在过程的开始。
作用
static的作用
在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
(1)先来介绍它的第一条也是最重要的一条:隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。
下面是a.c的内容
char a = 'A'; // global variable
void msg() {
printf("Hello\n");
}
下面是main.c的内容
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序的运行结果是:
A Hello
你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。
#include
int fun(void)
{
static int count = 10; // 此语句只在函数第一次调用时执行,后续函数调用此变量的初始值为上次调用后的值,每次调用后存储空间不释放
return count--;
},
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
(3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。不妨做个小实验验证一下。
#include
int a;int main(void)
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
程序的运行结果如下
integer: 0; string: (begin)(end)
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:
static 函数类型 函数名(函数参数表){……}
关键字“static”,译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。
使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
外部函数
外部函数的定义:在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,表示此函数是外部函数:
[extern] 函数类型 函数名(函数参数表){……}
调用外部函数时,需要对其进行说明:
[extern] 函数类型 函数名(参数类型表)[,函数名2(参数类型表2)……];
[案例]外部函数应用。
⑴文件mainf.c
main()
{
extern void input(…),process(…),output(…);
input(…);
process(…);
output(…);
}
⑵文件subf1.c
……extern void input(……) /*定义外部函数*/{……}
⑶文件subf2.c
……extern void process(……) /*定义外部 函数*/{……}
⑷文件subf3.c
……extern void output(……) /*定义外部函数*/{……}静态局部变量static的存储 有时希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为“静态局部变量”,用关键字static进行声明。用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可以先后为多个变量使用,节约内存),而且降低了程序的可读性,因此若非必要,不要多用静态局部变量。vb中语句
在过程级别中使用,用于声明变量并分配存储空间。在整个代码运行期间都能保留使用 Static 语句声明的变量的值。
static语句声明的变量,与dim语句声明的变量的主要区别是:前者只能在sub或function过程中使用,在退出sub或function过程后变量的值保留;后者使用在sub或function过程中时,退出sub或function过程后变量的值不保留。
语法Staticvarname[([subscripts])] [As [New]type] [,varname[([subscripts])] [As [New]type]] . . .
Static 语句的语法包含下面部分:
描述
varname 必需的。变量的名称;遵循标准变量命名约定。
subscripts 可选的。数组变量的维数;最多可以定义 60 维的多维数组。subscripts 参数使用下面的语法:
[lower To] upper [,[lower To] upper] . . .
如果不显式指定 lower,则数组的下界由 Option Base 语句控制。如果没有 Option Base 语句则下界为 0。
New 可选的。用它可以隐式地创建对象的关键字。如果使用 New 声明对象变量,则在第一次引用该变量时将新建该对象的实例,因此不必使用 Set 语句来对该对象引用赋值。New 关键字不能用来声明任何内部数据类型的变量,也不能用来声明从属对象的实例。
type 可选的。变量的数据类型;可以是
Byte、Boolean、Integer、Long、Currency、Single、Double、Decimal(目前尚不支持)、Date、String(对变长的字符串)、String * length(对定长的字符串)、Object、Variant、用户定义类型或对象类型。
所声明的每个变量都要有一个单独的 As type 子句。
说明
模块的代码开始运行后,使用 Static 语句声明的变量会一直保持其值,直至该模块复位或重新启动。可以在非静态的过程中使用 Static 语句显式声明只在该过程内可见,但具有与包含该过程定义的模块相同生命期的变量。
可以在过程中使用 Static 语句来声明在过程调用之间仍能保持其值的变量的数据类型。例如,下面的语句声明了一个定长的整型数组:
Static EmployeeNumber(200) As Integer
下面的语句为 worksheet 的新实例声明了一个变量:
Static X As New Worksheet
如果在定义对象变量时没有使用 New 关键字,则在使用该变量之前,必须使用 Set 语句将一个已有的对象赋给这个引用对象的变量。在被赋值之前,所声明的这个对象变量有一个特定值 Nothing,这个值表示该变量没有指向任何对象的实例。若在声明中使用了 New 关键字,则在第一次引用对象时将新建一个该对象的实例。
如果不指定数据类型或对象类型,且在模块中没有使用 Deftype 语句,则按缺省情况,定义该变量为 Variant 类型。
注意
Static 语句与 Static 关键字很相似,但是针对不同的效果来使用的。如果使用 Static 关键字(如 Static Sub CountSales ())来声明一个过程,则该过程中的所有局部变量的存储空间都只分配一次,且这些变量的值在整个程序运行期间都存在。对非静态过程而言,该过程每次被调用时都要为其变量分配存储空间,当该过程结束时都要释放其变量的存储空间。Static 语句则用来在非静态的过程中声明特定的变量,以使其在程序运行期间能保持其值。
在初始化变量时,数值变量被初始化为 0,变长的字符串被初始化为一个零长度的字符串 (""),而定长的字符串则用 0 填充。Variant 变量被初始化为 Empty。用户自定义类型的变量的每个元素作为各自独立的变量进行初始化。
注意 如果在过程中使用 Static 语句,应和其它的声明语句(如 Dim)一样将其放在过程的开始。
作用
static的作用
在C语言中,static的字面意思很容易把我们导入歧途,其实它的作用有三条。
(1)先来介绍它的第一条也是最重要的一条:隐藏。
当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c。
下面是a.c的内容
char a = 'A'; // global variable
void msg() {
printf("Hello\n");
}
下面是main.c的内容
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程序的运行结果是:
A Hello
你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。
如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。
(2)static的第二个作用是保持变量内容的持久。存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。
#include
int fun(void)
{
static int count = 10; // 此语句只在函数第一次调用时执行,后续函数调用此变量的初始值为上次调用后的值,每次调用后存储空间不释放
return count--;
},
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
程序的运行结果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
(3)static的第三个作用是默认初始化为0。其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加’\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是’\0’。不妨做个小实验验证一下。
#include
int a;int main(void)
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
程序的运行结果如下
integer: 0; string: (begin)(end)
最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0。
/*
2018-05-07
FIle:printf格式符号
功能:
1、验证各种输出格式符号
*/
#include
/*std: stdstandard英 [ˈstændəd] 美 [ˈstændərd]
n.标准,规格;旗,军旗;度量衡标准;直立支柱
adj.标准的,合格的;普遍的,一般的;公认为优秀的
*/
/*std: stdstandard英 [ˈstændəd] 美 [ˈstændərd]
n.标准,规格;旗,军旗;度量衡标准;直立支柱
adj.标准的,合格的;普遍的,一般的;公认为优秀的
*/
int main(void)
{
int i = 5;
printf("i = %d, i =%08d, i = %8d\n", i, i, i);
printf("i = %02d, i =%04d, i = %06d\n", i, i, i);
printf("i = %010d, i =%012d, i = %014d\n", i, i, i);
printf("i = %#d, i =%#o, i = %#x\n", i, i, i);
printf("i = %#d, i =%#8o, i = %#8x\n", i, i, i);
printf("i = %#0d, i =%#08o, i = %#08x\n", i, i, i);
return 0;
}
/*
在vc++6.0输出结果是:
i = 5, i =00000005, i = 5
i = 05, i =0005, i = 000005
i = 0000000005, i =000000000005, i = 00000000000005
i = 5, i =05, i = 0x5
i = 5, i = 05, i = 0x5
i = 5, i =00000005, i = 0x000005
Press any key to continue
*/
printf("i = %d, i =%08d, i = %8d\n", i, i, i);
printf("i = %02d, i =%04d, i = %06d\n", i, i, i);
printf("i = %010d, i =%012d, i = %014d\n", i, i, i);
printf("i = %#d, i =%#o, i = %#x\n", i, i, i);
printf("i = %#d, i =%#8o, i = %#8x\n", i, i, i);
printf("i = %#0d, i =%#08o, i = %#08x\n", i, i, i);
return 0;
}
/*
在vc++6.0输出结果是:
i = 5, i =00000005, i = 5
i = 05, i =0005, i = 000005
i = 0000000005, i =000000000005, i = 00000000000005
i = 5, i =05, i = 0x5
i = 5, i = 05, i = 0x5
i = 5, i =00000005, i = 0x000005
Press any key to continue
*/
8.1 指针是什么
int (*p)(int, int);
int max(int, int) // int min(int, int)
有p = max;
所以调用 c = max(a,b); 即:
1)c = (*p)(a, b);
2)c = p(a, b);
问题:两种方法导致的结果一样吗?均为正确使用函数指针的方法吗?
例子:
/* 处理中断 */
1)(*irq_array[bit])(bit); //我的程序--定时器中断课程
2)irq_array[bit](bit); //视频程序--定时器中断课程
答:通过vc++6.0和裸机定时器中断一课的程序可知,两种方法在两个平台均可成功使用!
注意:
1)vc++6.0中,
typedef int (*p1)(int, int); //需要另外声明;
typedef int (*p2)(int, int); //需要另外声明;
int (*p3)(int, int); //不需要另外声明;
即p1, p2使用前,需要声明:int (*p1)(int, int); int (*p2)(int, int)
问题2: typedef int (*p1)(int, int);
int (*p3)(int, int);
的区别?
答:typedef int (*p1)(int, int);是定义一种数据类型,而int (*p3)(int, int);是直接定义一种int (*)(int, int)类型的函数指针变量!