一.C语言的基本数据类型及运算
(一)标识符与关键字
1.32个关键字
auto,const,enum,extern,register,signed,sizeof,static,union,unsigned,void,volatile.
2.12个标识符作为编译预处理的命令单词,使用时前面加#
define,elif,else,endif,error,if,ifdef,ifndef,include,line,progma,undef
关键字或命令单词后必须有空格,圆括号,尖括号,双引号等分隔符,否则会与其他字符一起组成新的标识符
(二)数据类型
3.C语言的数据类型可分为基本类型(字符型,整型,实型--单双精度,无值型void),构造类型(枚举型enum,数组,结构体--含位段,共用体union),指针类型。
4.C语言数据类型的长度和值域
无值型(void)有两种用途:第一是明确地表示一个函数不返回任何值;第二是返回void*类型的指针,可指向任何类型的数据。
5.类型修饰符
signed 有符号 unsigned 无符号
long 长 short 短
整型数缺省状态为signed和short。
6.指针类型
动态分配内存,方便处理字符串,数组,调用函数时可得到“多于”一个返回值。
7.枚举型
“枚”是量词,相当于“个”,“举”是指将变量的值一一列举出来。实际上是用符号来表示若干个可取的整型值,它是整型的一个子集。
(三)常量
1.八进制整数以0作为开头
2.十六进制以0x为标志
3.一个二进制数0或1,成为位(bit)
4. 8个位称为一个字节(byte),每个字节存放在一个存储单元中,每个单元赋予一个存储地址。
5.用十进制数表示数据,十六进制数表示地址
6.单精度实型有小数和指数两种形式。
(1)小数形式:必须有小数点,如12.;实数仅有7位有效数字,超过7位的将是不精确的。如1.2345678将保存为1.234567,第八位将无法保留而失去,并不是四舍五入。当要求用五位小数表示时,则表达为1.23457,第七位向第六位四舍五入。
(2)指数形式:科学计数法。分成指数和尾数部分,尾数部分可以是整数形式或小数形式,指数部分是e后跟一个整数。由于实数仅有7位有效数字,因此在内存中用三个字节来表示尾数,用一个字节来表示指数,所以指数部分用两位整数来表示。如
-456.78e-01,4个字节。
7,双精度实型:有效位15~16位。双精度常量在内存中占8个字节。
(此处还有问题,为什么两个整数占一个字节)
8.字符常量
用单引号括起来的一个字符。以ASCII码形式存储在内存中。,每个字符在内存中占一个字节。
9.控制字符常量或转义字符常量:单引号内以“\”开头后跟转义字符,或八进制,十六进制数,它们是一类不可打印字符,代表某些功能。
10.字符串常量
用一对双引号括起来的字符。字符串常量由系统在字符序列最后加一个字符'\0'来表示。 \0表示空NULL;
(四)变量(先定义,后使用)
1.在同一层次的变量,不能与数组,指针,函数或其他变量同名。
2.字符型变量:仅占一个字节,其内存中存放的是该字符的ASCII。
3.枚举型变量:
(枚举型是整型常数的集合,这些常量指定了所有该类型变量可能具有的各种合法值)
enum <枚举类型名> {枚举元素表} <变量表>;
枚举类型名和变量表是选择项
例如:enum weekday {sun,mon,tue,wed,thu,fri,sat} ; weekday是枚举类型名,花括号内是该枚举类型变量可能具有的各种情况的一一列举;
enum weekday workday ,restday ; 定义枚举类型变量,即变量weekday,restday是属于enum weekday枚举类型的,该枚举型变量只能赋予花括号内的常量。
枚举类型的定义有三种形式:
(1)枚举类型和枚举变量分别定义;
(2)合并成一句;
(3)当只有一种枚举类型时,可省略枚举类型名;
花括号内的元素称为枚举元素或枚举常量,他们是用户定义的标识符,并不自动的代表什么含义。
枚举元素实际上是用它们所对应的整型数来代替,即枚举类型只是整型的一个子集,且可以在任何一个整型表达式中使用这些枚举值。具体的枚举元素所对应的整数由两种情况所决定:(1)缺省:当花括号内的枚举元素没有被初始化,第一项代表0,第二项代表1,以此类推。
(2)初始化:我们可以用初始化来改变枚举元素的相应值。
初值可以从任何一个整数开始,也可以指定几个初值,也可以给任何一个枚举常量赋初值。
枚举型变量值在输出时是输出其整常数而不是其枚举元素的标识符。枚举型变量在赋值时可以赋枚举元素而不能直接赋整型常量,如要赋整型常量,则要进行类型转换。
如:restday = (enum weekday) 6; 或 restday = sat;
4.变量的初始化
(1)全程和静态变量在程序编译阶段初始化,且只赋一次值。而局部变量是在进入定义它们的函数或复合语句时才做初始化,相当于赋值语句。每调用一次,就赋值一次。
(2)所有的全程和静态变量在没有明确初始化的情况下由程序自动赋0.而局部变量和寄存器变量在未初始化时其值是不确定的,即保持原来的状态不变。
(五)运算符
1.求字节数运算符(sizeof)
2.++,--用于整型和指针变量(取下一地址)
3.关系运算符的运算结果是逻辑量,0和非0.
4.在逻辑表达式的求解过程中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的值时,才执行该运算符。(执行与否会影响后续变量的值)
5.位运算符:位运算是对字节或字(char,int等)中的实际二进制位进行检测,设置或移位。位运算不能用于float,double,void或其它更复杂的数据结构。
&与 ^异或 |或 ~反 <<左移 >>右移
6.赋值语句从右向左结合。先计算右边表达式的值,再转换成表达式左边变量的类型,再进行赋值。
7.表达式中的类型转换:当不同类型的常量和变量混合使用时,它们最终会转换成同一类型,向上靠。不同类型数据计算前自动转换成相同的类型,再进行计算。
(1)转换结果必定是int,long,double.
(2)各类型级别由低到高的顺序为char,int,unsigned,long,unsigned long,float,double.有符号和无符号数进行混合运算,结果为无符号类型。
例:float x; int i; x = i = 3.14159
i = 3; x = 3.0
二.C程序设计初步
(一)结构化程序设计思想
(二)C语句概述
1.空语句仅有一个分号,常用语控制语句必须出现语句之处,不做任何操作,只在逻辑上起到一个语句的作用。
(三)赋值语句
(四)数据输出
把数据送到stdout标准输出流。
几个标准输出库函数:
1.putchar(ch);单个字符输出
ch可以是整型变量,此时仅输出低字节所代表的字符。
2.printf()格式化输出函数
输出八进制整数 %o 十六进制整数 %x %p:显示一个指针地址
(1)%md 最小宽度为m位,一般右对齐,少于m位左补空格或0,数据要左补0,则在m前面加个0. %0md
(2)%m.nf,m为总宽度(包括小数点),n为小数部分位数,若小数部分位数超过n位,则n+1位向n位四舍五入,整个数据小于m位左补空。
%m.ne 小数部分n位(包括e在内),全部长度取m位。
(3)%m.ns m为总长度,n表示只取字符串左端的n位。n<m时,左端补空格。
(4)-表示左对齐格式。
(5)l 在输出d,i,o,u,x等整型量时,在其前面加上l表示输出的是长整型数;在e,f,g等实型量前加l表示输出的是一个双精度实型数。
(6)一个整型数可以用不同格式来输出。
(7)一个实数可以按%f和%e来输出,如果按%f来输出,则能输出全部整数,并保留6位小数,但有效位只有前5位。如果按%e输出,则系统自动给出6位小数,小数点前有一位非零整数,后跟一个e,指数的正负符号位占一位,数值部分占2位。
如123.456 %f 123.455994 六位小数,但有效位只有前五位(包括整数部分但不包括小数点)
%e 1.23456e+02 尾数总共只有六位,小数点前一位非零的。
(8)无符号十进制数的输出 %u -1 --> 65535 (尚不明确) -2 ==》65534
3.puts()函数(字符串输出函数)
char * puts(char *s);
puts()函数将字符串数据写在屏幕上并换行。它仅用来输出一字符串,不能输出数值也不能进行格式变换,也可以输出转义字符。
(五)数据输入
1.getche()与getchar(),getch()
ch = getche(); 等待从键盘输入一个字符,返回它的值并在屏幕上自动回显该字符,必须使用<conio.h>。(即输入时回显)
getchar():它的输入缓冲区一直到键入一个回车符才返回给系统,这样就可能在getchar()返回之后还留下一些字符在输入排队流中。(建议不用)
getch():不把读入的字符回显,避免不必要的显示。<conio.h>
2.scanf()函数:输入具有某种格式的数据
(1)在格式控制字符串中的一个空白字符会使scanf()函数在读操作中略去输入流中的一个或多个空白字符。输入数据必须用空格,制表符或回车来分隔。
(2)格式说明还可以带修饰项,如确定输入最大位数,可少于,当多于时只读入该数所表示的位数,多余数据将作为下一个数据读入其他变量。
(3)一个格式说明符中出现“*”修饰符时,表示读入一个该类型的数据并不存储(即跳过该数据)
(4)%s读入字符串时,参量表内必须是字符数组名或字符指针,在使用字符数组名或指针名时,前面不必加&,因为数组名是数组存储的起始地址,指针内存放的也是地址。
(5)数据输入时不能规定精度。
3.gets()字符串输入函数
必须用回车作为数据输入结束,该回车符并不属于这串字符,由一个空操作符(\0)在串的最后来代替它。此时空格不能结束字符串的输入,返回一个指针。
三.分支结构的C程序设计
(一)分支结构中的表达式
1.C语言中的逻辑值:非零为真,只有0和'\0'(其ASCII值为0)为假
2.关系表达式的运算结果:1为真,0为假
3.闰年:能被4整除但不能被100整除,或能被400整除
(二)if语句
1.简单形式:if( 表达式 ) 语句;
2.if~else结构:
3.条件运算符中的语句可以是函数。
4.嵌套最多可达15层
5.if else if else if ...... else
(三)switch语句
switch(表达式) #表达式可以是整型,字符型,枚举型
{
case 常量一: 语句段1;[break]
default:语句段n
}
计算表达式的值,同case后的常量比较,相等则执行后面的语句。如果没有break,则执行后面所有的语句。
如果没有default,匹配不成功时什么也不执行。
每个case分支可有多条语句,但不必用{}。最多可有257个常量。
当若干分支需要执行相同操作时,可利用空语句,将几个case分支写在一起。
case后面的常量必须是字符常量。
五.循环结构的C程序设计
(一)while语句
(二)do-while语句
(三)for语句中的任意一个表达式都可以省略,但封号一定要保留。
(四)break语句:只能用于switch语句或循环结构。只能跳出它所在的循环,不能跳出多层循环。
continue:结束本次循环,即跳过循环体中尚未执行的语句,直接进行下一次是否执行循环的判定。
六.数组
(一)一维数组
1.C语言不允许定义动态数组,即数组的长度不能依赖于程序运行过程中变化着的量。因为C语言是在编译阶段为数组开辟单元,而运行时才能得到的变量值远晚于编译阶段,是无法实现的。
2.先定义后使用,只能逐个引用数组元素,不能一次引用整个数组。
3.C编译系统对数组下标越界并不给出错误提示。
4.一维数组初始化:
int s[5]={67,37,90},则后两个数值为0.若对全部数组元素赋初值,可以不指定数组长度。 int s[]={1,2,3,4,5};
数组占有连续的存储单元。
(二)二维数组
1.int a[3][2] 三行两列 ,二维数组的排列顺序是按行存放的,即在内存中,先顺序存放第一行,再顺序存放第二行。
2.二维数组初始化:可以用分行赋值:int a[3][2]={{1,2},{2,3},{3,4}};部分元素赋初值时,其余元素自动赋0.
也可以将所有数据写在一个花括号内,按数组的排列顺序对各元素赋初值。
若对全部元素都赋初值,则定义数组时第一维度的长度可以不确定,但第二维度的长度不能省。
若没有初始化,则定义数组时,所有维的长度都必须给出。
(三)字符数组与字符串
1.内存中存放的是用整数表示的该字符的ASCII码。
2.只有字符串常量的概念,没有字符串变量的概念。用字符数组来处理字符串。
3.字符串常量是用双引号括起来的字符序列。在数组中末尾存放一个字符'\0'(ASCII为0),用它来作为字符串结束的标志;
4.串的长度不包括'\0';定义数组存放字符串时应保证数组长度大于将存放的字符串长度。
5. 字符串初始化:char c[]={"string"};或char c[]="string";或c[]={'s',........,'\0'};
(四)常用字符串处理函数
1.gets字符串输入函数:gets(字符数组)
从标准输入文件中读取一个字符串到字符数组中。它读取字符串直到遇到换行符'\n',并将换行符转换为字符结束输入标志符'\0'存放到字符数组中。
将空格也作为元素读入,不同于scanf
2.puts字符串输出函数:在输出时将'\0'转换为换行符,即输出字符后换行。
3. scanf字符串的输入是以“空格”,“Tab”或“回车”来结束输入。同时输入多个字符串时,字符串之间以“空格”为间隔,最后按“回车”结束输入
七.函数及变量存储类型
(一)函数基础与C程序结构
1.C程序的结构化设计思想
将一个复杂的任务划分为若干子任务,每个子任务设计成一个子程序,成为模块。
C语言是函数式语言,没有子程序,利用函数来实施结构化设计程序。
组成一个C程序的各函数可以分开编辑成多个C源文件。一个C源文件有0个或多个函数。
2.函数概述:c程序总是从main函数开始执行。
函数声明:指出函数原型,包括函数名,参数和返回值类型。
(二)函数的定义和声明
1.函数定义的一般形式:存储类型标识符 类型标识符 函数名(形参及类型说明)
存储类型标识符说明函数的存储类型,它规定了函数可被调用的范围。可用于函数的存储类型标识符有static和extern,指定为static的函数为静态函数,
静态函数只能由和它在同一文件中定义的函数调用;不指定存储类型标识符时为缺省状态的存储类型extern,为外部函数。
2.不能定义返回数组的函数。
3.外部函数的名字要作用于整个程序,因而外部函数相互之间不能重名。静态函数可以和外部函数同名,但同一文件中的函数不能同名。
4.return 表达式;或return (表达式);
5.对于基本类型,表达式的类型和函数的类型不相同时表达式的值自动转换为函数的类型。对于指针,须使用类型强制符将表达式的值转换为函数的类型;对于结构体,表达式值的类型与函数定义的类型必须相同。
6.C语言允许函数先调用后定义,或被调用函数在其它文件中定义。
7.非int函数,必须在调用函数之前作函数声明,其目的是指出被调用函数的类型和参数类型,否则编译程序认为被调用函数为int类型。(最新C++库不再默认为int)
函数声明一般格式:存储类型标识符 类型标识符 函数名(形参表);
外部函数声明可指定extern或存储类型标识符缺省,静态函数声明必须指定static;参数表可以只列出参数的类型名而不需给出参数名。声明时给出的参数名被编译忽略,因为参数的存储分配是在函数被调用时进行的。
8.函数声明可位于调用函数体内或函数体外。在函数体外声明的函数可在声明之后直至该源文件结束的任何函数中调用,在函数体内声明的函数只能在声明所在的函数体内调用。
(三)函数的调用
1.函数调用在程序中起一个表达式或语句的作用。
2.对于无返回值函数的调用,只能以语句形式出现。
3.被调用函数的定义出现在主调函数之前。
4.形参与实参的数值传递
C语言中,参数的传递方式是“单向值传递”,形参和实参变量各自有不同的存储单元,被调用函数中形参变量值的变化不会影响实参变量的变化。
例如:
void swap(int x,int y)
{
int z;
z=x;x=y;y=z;
}
int main()
{
a=10;b=20;
swap(a,b);
}
a=10,b=20;
原因:a=10 --> x=10 ; b=20 --> y=20 ; ------ 结果 a=10 -->x=20 ; b=20 --> y=10
5.C语言中可以定义参数数目可变的函数。至少要给出一个形参,后面列三个点。
6.多数编译程序在计算参数值时按从右到左的顺序。
7.如果函数的类型不是void,既是函数没有return语句,函数也有返回值,只是返回值为不确定的值。
(四).变量的存储类别
1.C语言的变量有两种属性:数据类型和存储类型。因此完整的变量说明的一般形式为:存储类型标识符 类型标识符 变量名;
2.C语言的四种存储类型:auto(自动),extern(全局),static(静态),register(寄存器)。
3.动态存储和静态存储
内存中供用户使用的存储空间可分为程序区,动态存储区和静态存储区。
程序区用来存放程序代码,动态和静态存储区用来存放数据,即数据与处理数据的程序是分离的。
静态存储区即全局数据区,存放全局数据和静态数据。
动态存储区可分为堆区和栈区。堆区用来存放程序的动态数据,栈区用来存放程序局部数据,即各个函数中的数据。
动态存储区生存期为程序运行的某个阶段,静态存储区数据为整个程序运行过程。
4.局部变量
局部变量的存储类型可以通过类型标识符auto和static来规定。
auto--动态存储区--可以缺省;static--静态存储区。
编译器并不将局部自动变量预置位0;
局部变量还可以与全局变量同名,此时在局部变量的作用域内,全局变量不起作用。
5.局部静态变量的使用
在函数调用结束后,该变量虽然仍在内存中存在。局部静态变量可以用来优化程序。
6.全局变量
全局变量是在所有函数之外定义的,静态存储区中。
通过extern作引用说明,全局变量的作用域可以扩大到整个程序的所有文件。
用static,与普通全局变量的区别是它的作用域。普通全局变量对文件中的所有函数可见,而且还能被其它文件中的函数所用;static型的仅对其所在文件定义后的函数可见,不能被其它文件使用。
全局变量初始化只执行一次,若无显式初始化,则由系统自动初始化为与变量类型相同的0初值。
7.寄存器变量
(五).编译预处理
1.宏定义
#define 名字 替换文本
(1)不带参数的宏定义:#define 标识符 字符串 一般替换文本是#define指令所在行的剩余部分,但也可以为若干行,在待续行后加反斜杠\即可。
宏定义不是C语句,不加分号;可以用#undef命令终止宏定义的作用域。
可以用已定义的宏名,并层层替换;对程序中用双引号括起来的字符,不进行替换。
(2)带参数的宏定义:不仅是简单的字符串替换,还要进行参数替换。 #define 宏名(参数表) 字符串 如:#define S(a,b) a*b
宏名与带参的括号之间不应加空格,否则将空格以后的字符串都作为代替字符串的一部分
定义宏时,最好将参数和宏体用括号括起来。 如:square(n) n*n s=square(a+1) s-a+1*a+1,与预期效果不同
2.文件包含处理
一个文件可以将另一个文件的全部内容包含进来。
一个include只能包含一个文件;1包含2,2要用三,则在1中用两次include,而且3要 在2之前。
文件包含是可以嵌套的;
#include命令中,使用双引号,系统首先在引用被包含文件的源文件所在目录中查找要包含的文件,若未找到,再按系统指定的标准方式检索其它目录;
用尖括号时,不检查源文件所在的目录而直接按照系统标准方式检索目录文件。
一般用双引号比较保险。
一般地,将系统提供的库文件采用尖括号表示,而用户自定义的文件采用双引号。
3.条件编译
对一部分内容指定编译的条件;
(1)#ifdef 标识符
程序段1
#else
程序段2
#endif
如果标识符已被#define命令定义过,则对程序段1进行编译;否则对程序段2编译;
(2)#ifndef 标识符
程序段1
#else
程序段2
#endif
如果标识符未被定义,则对程序段1进行编译。。。。。。。。。。
(3)#if 常量表达式
程序段1
#else
程序段2
#endif
指定表达式非0,就编译程序1;
八.指针
(一)指针的概念与定义
1.指针就是用来存放地址的变量。某个指针存放了哪个变量的地址,就说该指针指向了这个变量。
2.变量的地址:&变量名
3.存放变量地址的变量称为指针;被pc指向的变量c称为pc的对象;对象就是一个有名字的内存区域,即一个变量。
4.指针的定义:类型名 *指针名;
5.指针要有明确的当前指向,通过初始化或赋值来完成。
6.*:指针运算符(或称“间接访问运算符”)
7.&和*二者的优先级相同,结合方向为自右至左。
(二)指针作函数参数
a=10,b=20;
void swap(int *pa,int *pb)
{
int temp;
temp = *pa; *pa=*pb; *pb=temp;
}
形参的改变无法返回给实参,但利用形参指针对其所指向单元内容的操作,就有可能改变主调函数中变量的值。
(三)指针与数组
1.数组名成为符号常量,其值为数组在内存中所占用单元的首地址;即数组名代表了数组的首地址。
指针存放数组中第一个元素的地址时,该指针指向了这个数组。
2.pa=&a[0]或pa=a;
3.指向数组的指针加1等效于数组元素下标加1.
4.C允许:pa[i]和*(a+i),等价于*(pa+i)和a[i].
5.a=pa;a++;pa=&a都是非法的。
6.数组作函数参数
将数组的首地址作为函数参数传递时,有两种方式:指向数组的指针,和数组名;
形参和实参都是指针时,实参存放的是某个数组的首地址,并把这个地址传递给形参。
实参是数组名,形参是指针时,同类型的常量实参传递给变量形参,此时,对形参指针所指向的内容的访问就是对数组的访问。
形参和实参都是数组时名,虽然有两个数组名,但是只有一个数组,C不会给形参数组再开辟一个内存单元,而是认为形参数组名是实参数组名的别名,也就是说,对形参数组的操作就是对实参数组的操作。
实参是指针,形参是数组名,系统认为实参指针是指向某个数组的,此时形参数组与实参所指向的数组是同一数组,且为该数组的别名。
for(i=0;i<10;i++)
scanf("%d",p++)
x是指针,用它访问数组元素时可以用下标法,也可以用指针法。
指针与数组的几种等价:a[i] --> *(pa+i) --> *(a+i) -->pa[i] a==pa
7.指针和字符串
char *point="I am a stu";
char *point; point="I am a stu"; 字符串常量的值就是该字符串在内存中的首地址。
char string[]=""; char *point=""; 第一个为常量,不能赋值;第二个可以赋值。
字符串拷贝函数:
void my_strcpy(char *t,char *s)
{
while((*t=*s)!='\0')
{
s++;
t++;
}
}
8.指向多维数组的指针
a[2][4] 数组a看成一维数组,包含两个元素;
a[0]和a[1]虽然没有显式的定义,但它们可以被认为是数组名,是数组在内存中的首地址,这一点与数组名a一样,与a不同的是类型。
表示形式 类型 含义
a,&a[0] 行地址 第0行地址
a+1,&a[1] 行地址 第1行地址
a[0],*a 列地址 第0行第0列地址
a[1],*(a+1) 列地址 第1行第0列的地址
a[1]+2,*(a+1)+2,&a[1][2] 列地址 第1行第2列的地址
a[1][2],*(a[1]+2),*(*(a+1)+2) 整形 第1行第2列元素
对行地址进行一次指针运算就成为列地址,而对列地址进行一次取地址运算就成为行地址。
定义行指针: 类型名 (*指针名)[数组长度];(两个约束条件:一是所指向数组的类型,二是每行的行数)
*(*(p+i)+j) p[i][j] (*(p+i))[j]
多维数组作函数参数的问题:
(1)形参说明为指向数组元素的指针,实参为数组元素的地址或指向元素的指针。
(2)形参说明为行指针,实参为行地址或行指针。
9.指针数组
由指针变量组成的数组称为指针数组。
类型名 *数组名[常量表达式];
指针数组的主要用途是表示二维数组,尤其是表示字符串的数组。 用指针数组表示二维数组的优点是:每一个字符串可以具有不同的长度。
如: int *pa[4],a[4][4];
pa[0] = &a[0][0]或a[0];
pa[1] = &a[1][0]或a[1];
引用a[i][0],*pa[i],*(*(pa+i)+0),*(pa[i]+0).
(四)指针与函数
1.指针指向函数,即指针存放函数的入口地址;返回值为指针。
2.指向函数的指针
(1)通过指针来访问函数
类型标识符 (*指针名)(); 定义指向标识符类型的函数。
一个函数的入口地址由函数名表示,它是函数体内第一个可执行语句的代码在内存中的地址。
把函数名赋给一个指向函数的指针,就可以用该函数型指针来调用函数。
C允许将一个函数名作为参数传递给另一个函数。
如定义三个函数 int max(),min(),sun();
void operate(int x,int y,int (*fun)())
3.返回指针的函数
C的函数可以返回除数组。共用体变量和函数以外的任何类型数据和指向任何类型的指针。
注意:指针函数与指向函数的指针的区别。后者:(*a)();
(五)复杂指针
1.指向指针的指针
char **p_p;(即*(*p_p)
int a[10],*p; 可以认为a和p的类型相同。
int *aa[10],**p_p; 可以认为aa和p_p类型相同。
2.命令行参数
在支持C语言的环境中, 当程序开始运行时可以将命令行参数传递给它。运行程序相当于调用main()函数。
main()可以有两个参数:第一个(通常命名为argc)是程序所调用的命令行参数的个数;第二个(通常命名为argv)是一个指针数组,数组
中的每个元素指向命令行的每个字符串。
如:echo程序 int main(int argc,char *argv[])
程序运行命令行情况:echo hello world回车
argv[0]为调用程序的名字,所以argc至少为1.
一般情况下要求argv[argc]为空指针。
利用命令行参数来确定程序的操作或流程。
3.复杂指针的理解
注意事项:
(1)数组的元素不能为函数(可以为函数的指针)。
(2)函数的返回值不能为数组或函数(可以为函数或数组的指针)。
理解复杂说明符:从标识符开始,按照运算符的优先级和结合性顺序逐步解释。
九.结构体和共用体
(一)结构体
1.构造类型与基本类型的本质区别是它们不是全新的数据类型,而只是对基本类型的“封装”,由基本类型“构造”而来。
2.结构体类型的定义:自己先定义结构体类型(即结构体的组成)
关键字和类型名字组合成一种新的类型标识符,类似int。
3.结构体型变量的定义
结构体类型的定义本身不会创建任何变量,它只是一种模板,限定了这种类型的结构体变量的组成样式。
在编译时,结构体类型并不分配空间,只对结构体类型的变量分配空间。一般占用一片连续的存储空间。
4.结构体型变量及其成员的引用
它是一种聚合性变量,可访问的对象有结构体型变量名本身和成员名。
(1)访问结构成员:结构体变量名.成员名 &(work.cost)等于&work.cost
(2)相同结构体类型的结构体可以通过整体引用来赋值,但不允许对结构体型变量进行任何逻辑运算。如果要比较,只能逐个成员比较。
也不可以对结构体型变量进行整体的输入/输出,只能逐个输入输出。
(3)结构体型变量占据的一片存储单元的首地址称为该结构体型变量的地址,其每个成员占据的若干单元的首地址称为该成员的地址,两个地址均可用。
结构体型变量的地址主要用作函数参数。
5.结构体型变量的初始化:struct staff worker1={"wangwei",1200,13};
未初始化的成员不赋0.
(二)嵌套结构
1.允许一个结构体包含另一个结构体或者把一个结构体成员定义为数组。
2.内部结构体类型初始化时用花括号括起来。
(三)结构体型数组
1.结构体型数组的初始化:每个元素的初值由{}括起来。
2.“.”优先于&和*。
(四)结构体型指针
1.用指针访问结构体成员: (*指针名).成员名
指向成员运算符:-> 指针名->成员名 ->优先于++
(五)结构体与函数
1.结构体作函数参数
结构体型变量,变量的成员,指针都可作为参数。
单向传递,即处理结果是带不回来的。
2.结构体作为函数返回值
函数在处理结构体类型时,可以将其模块化为简单类型来计算结果。
(六)内存的动态分配
1.开辟和释放内存区的函数
malloc函数 void *malloc(unsigned size) 在内存的动态区分配长度为size个字节的连续空间。
分配成功,返回分配空间的起始地址;否则返回空指针。 强制类型转换
free(p)函数 释放由p指向的内存区
void *calloc(unsigned n,unsigned size) 分配n个大小为size个字节的连续空间,用于动态数组的分配
void *realloc(void *p,unsigned size) 将p所指出的已分配的内存空间重新分配成大小为size个字节的空间。用于改变已分配空间的大小,可以增减单元数。
(七)共用体(联合)
1.关键词union 标识符{} 共用一段内存
2.共用体不能整体引用。 引用.
3.给一个新的成员赋值就冲掉了原有成员的值,每个瞬间都只有一个变量起作用。
4.共用体型变量的地址和它的各成员的地址同值,&data,&data.i,&data.ch.
5.不能在定义共用体型变量时对其初始化。 不能作为函数参数或函数返回值,但可以使用指向共用体型变量的指针。
(八)位段
1.C语言的特点之一是可以代替汇编语言编写系统程序。
2.位段是一种特殊的结构体类型,其每个成员是以位为单位来定义长度的,不再是各种类型的变量。
例如:struct packed_data{ unsigned x:1; unsigned y:2; unsigned z:3;}bits;
该结构体定义了一个长度为1位,2位,3位的位段,1,2,3,说明了相应成员所占存储单元中二进制的位数。
3.一个位段必须被说明成int,unsigned,signed中的任一种,长度为1的位段被认为是unsigned类型,因为单个位不可能具有符号。
4.位段中成员的引用与结构体引用相同,“.”。
5.可定义无名位段: struct{ unsigned a:1; unsigned b:2; unsigned :5;//此5位无名,不用 unsigned c:8;};
6.一个位段必须存储在同一存储单元中,不能跨两个单元,所以位段总长不能超过整型长度的边界。
7.位段不能定义成数组。
8.位段可在表达式中被引用(按整型数),也可以用整型格式符输出。
(九)类型定义
1.允许用户用类型定义将已有的类型标识符定义成新的类型标识符,新的类型标识符可当做原标识符使用。
2.typedef typename identifier; 如:typedef int A; A i;
3.用typedef还可以定义其它各种已经定义过的类型。
(1)数组: typedef char STRING[80]; STRING s1,s2; <==> char s1[80],s2[80];
(2)指针: typedef float *PFLOAT; PFLOAT p1,p2; <==> float *p1,*p2;
(3) 函数: typedef char FCH() FCH af; <==> char af();
typedef FCH *PFCH; PFCH bf; <==> char (*bf)();
4.不能直接定义变量,可以嵌套定义。
十.文件
(一).文件的概念
1.根据文件内数据的组织形式,文件可分为文本文件(ASCII文件)和二进制文件。
2.源程序文件.C,经编译后得到的目标文件.OBJ,连接之后形成的可执行文件.EXE.
3.在头文件stdio.h中,定义了一个名为FILE的类型,包含了所有与文件操作有关的数据成员。有了FILE类型后,可以定义文件型指针。
4.文件由磁盘文件和设备文件组成。磁盘文件之一为数据文件。 一切能进行输入/输出的终端设备-->设备文件 如键盘为标准输入文件
(二)文件的打开与关闭
1.文件的打开 fopen()
FILE *fp; fp=fopen(文件名,使用文件方式);
文件使用方式 含义
"r"(只读) 为输入打开一个文本文件
"w"(只写) 为输出打开一个文本文件 若原来文件不存在,则在打开时建立一个按指定名字命名的文件。如果原来已存在一个以该文件名命名的文件,则在打开时将该文件删去,然后重新建立一个新文件。
"a"(追加) 向文本文件尾增加数据 文件必须存在
"rb"(只读) 为输入打开一个二进制文件
"wb"(只写) 为输出打开一个二进制文件
"ab"(追加) 向二进制文件尾增加数据
"r+"(读/写) 为读/写打开一个文本文件
"w+"(读/写) 为读/写建立一个新的文本文件
"a+"(读/写) 为读/写打开一个文本文件
"rb+"(读/写) 为读/写打开一个二进制文件
"wb+"(读/写) 为读/写建立一个新的二进制文件
"ab+"(读/写) 打开一个二进制文件,允许读,或在文件末追加数据
2.常用下面的程序打开一个文件
if((fp=fopen("file1","r"))==NULL)
{
printf("Can not open this file\n");
exit(0); /关闭所有文件,终止正执行的程序
} //先检查打开操作是否正确
3.在用文本文件向计算机输入数据时,将回车换行符转换为一个换行符,在输出时把换行符转换成回车和换行两个字符。在用二进制文件时,不进行这种转换,内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。
4.文件的关闭 fclose()
fclose(文件指针).
如果程序终止之前不关闭文件,将可能丢失缓冲区最后一批未处理的数据,因为fclose()的调用不仅释放文件指针,还刷新缓冲区。
程序结束时会自动关闭文件。
fclose()函数也返回一个值:0表示关闭成功,EOF则表示出错。
(三)文件的读/写
1.fputc()函数和fgetc()函数
fputc(ch,fp);将字符(ch的值)输出到fp所指向的文件中。 输出成功,返回输出的字符;失败则返回EOF(-1)
ch=fgetc(fp); 从指定文件读入一个字符,该文件必须是以读或读/写方式打开的。如果在执行过程中遇到文件结束符,函数则返回一个文件结束标志EOF,可以利用它来判断是否读完了文件中的数据。
如果想从一个磁盘文件按顺序读入字符并在屏幕上显示出来,可编程为
while((ch=fgetc(fp))!=EOF)
putchar(ch); //不再适用
feof()函数来判断文件是否真的结束。 feop(fp)用来测试fp指向的文件当前状态是否为“文件结束”,是返回1,否返回0.
2.fgets()函数与fputs()函数
fgets(str,n,fp);从fp指向的文件读入n-1个字符,并把放到字符数组str(也可以是字符指针)中,如果在读入n-1个字符结束之前遇到换行符或EOF,读入即结束。
字符串读入在最后加一个'\0',fgets()返回值为str的首地址。
fputs(str,fp);将字符串输出到指定的文件。若调用成功,返回0,否则返回非0值。
3.fprintf()函数和fscanf()函数
fprintf(文件指针,控制字符串,参量表);
fscanf(文件指针,控制字符串,参量表);
4.fread()函数和fwrite()函数 --结构体型变量
fread(buf,size,n,fp);
fwrite(buf,size,n,fp);
buf是一个指针,它是读入数据将要存放的起始地址,或输出数据的起始地址。
size是要读/写的一个数据项的字节数。
n是要进行读/写数据项的个数。
调用成功,则函数返回为n的值;否则返回一个不足的计数。
(四)文件的定位
1.文件中有一个位置指针指向下一个要读的数据,成为当前工作指针。每读/写完一个数据,该位置指针自动向后移动一个数据的位置,这就是顺序读/写。
2.rewind()函数
强制使当前工作指针指向文件的开头。一般在要重新从头读/写文件时使用。rewind(fp);
3.fseek()函数
fseek(文件类型指针,位移量,起始点);
控制文件位置的指针进行随机读写。
起始点 常量表示 数字表示
文件首 SEEK_SET 0
当前位置 SEEK_CUR 1
文件尾 SEEK_END 2
位移量指从起始点向前移动的字节数。
fseek()函数一般用于二进制文件,因为文本文件要发生字符转换,计算位置时容易发生混乱。
4.ftell()函数
得到流式文件中位置指针的当前位置,用相对于文件开头的位移量表示。
若返回-1L,表示函数调用出错。
库文件
(一)<math.h>
1.int abs(int x) 求整数x的绝对值
2.double fabs(double x) 求双精度实数x的绝对值
3.double acos(double x) 计算cos^(-1)(x)的值 x在-1~1范围内
4.double asin(double x) 计算sin-1(x)的值 x在-1~1范围内
5.double atan(double x) 计算tan-1(x)的值
6.double atan2(double x) 计算tan-1(x/y)的值
7.double cos(double x) 计算cos(x)的值 x的单位为弧度
8.double cosh(double x) 计算双曲余弦cosh(x)的值
9.double exp(double x) 求ex的值
10.double floor(double x) 求不大于双精度实数x的最大整数
11.double fmod(double x,double y) 求x/y整除后的双精度余数
12.double frexp(double val,int *exp) 把双精度val分解尾数和以2为底的指数n,即val=x*2n,n存放在exp所指的变量中,返回位数x 0.5≤x<1
13.double log(double x) 求㏑x
14.double log10(double x) 求log10x
15.double modf(double val,double *ip) 把双精度val分解成整数部分和小数部分,整数部分存放在ip所指的变量中,返回小数部分
16.double pow(double x,double y) 计算x^y的值
17.double sin(double x) 计算sin(x)的值
18.double sinh(double x) 计算x的双曲正弦函数sinh(x)的值
19.double sqrt(double x) 计算x的开方
20.double tan(double x) 计算tan(x)
21.double tanh(double x) 计算x的双曲正切函数tanh(x)的值
(二)<string.h>
1.strcmp字符串比较函数 strcmp(字符1,字符2)
2.strcpy字符串拷贝: strcpy(字符数组1,字符2,n),将字符2的前n个字符拷贝到数组1中,字符数组1必须足够大。
3.strcat字符串连接函数:字符2连接到字符1的后面,结果保存到第一个字符中。
4.strlen字符串长度,不包括'\0';
5.strlwr:大写字母变小写
6.strupr:小写字母变大写
(三)<stdlib.h>动态分配和随机函数
1.void *free(void *p) 释放p所指的内存区
2.void *malloc(unsigned size) 分配size个字节的存储空间
4.void exit(int state) 程序终止执行,返回调用过程,state为0正常终止,非0非正常终止
5.void *calloc(unsigned n,unsigned int size) 分配n个数据项的连续内存空间,每个数据项的大小为size字节
6.void **realloc(void *p,unsigned int newsize) 把p所指内存区的大小改为size个字节
7.int rand(void) 产生随机数
8.void srand(unsigned seed) 初始化随机数发生器,srand()以给定数进行初始化。
(四)<time.h>
(1).概念
1.Coordinated Universal Time(UTC):协调世界时,又称为世界标准时间,也就是大家所熟知的格林威治标准时间(Greenwich Mean Time,GMT)。比如,中国内地的时间与UTC的时差为+8,也就是UTC+8。美国是UTC-5。
2.Calendar Time:日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同,但对一个编译系统来 说,这个标准时间点是不变的,该编译系统中的时间对应的日历时间都通过该标准时间点来衡量,所以可以说日历时间是“相对时间”,但是无论你在哪一个时区, 在同一时刻对同一个标准时间点来说,日历时间都是一样的。
3.epoch:时间点。时间点在标准C/C++中是一个整数,它用此时的时间和标准时间点相差的秒数(即日历时间)来表示。
4.clock tick:时钟计时单元(而不把它叫做时钟滴答次数),一个时钟计时单元的时间长短是由CPU控制的。一个clock tick不是CPU的一个时钟周期,而是C/C++的一个基本计时单位。
(2).计时
C/C++中的计时函数是clock(),而与其相关的数据类型是clock_t
clock函数定义如下: clock_t clock( void );
这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock)。其中clock_t是用来保存时间的数据类型, clock_t是一个长整形数。在time.h文件中,还定义了一个常量CLOCKS_PER_SEC,它用来表示一秒钟会有多少个时钟计时单元,其定义如下:
可以看到每过千分之一秒(1毫秒),调用clock()函数返回的值就加1。
下面举个例子,你可以使用公式clock()/CLOCKS_PER_SEC来计算一个进程自身的运行时间:
void elapsed_time()
{
printf("Elapsed time:%u secs./n",clock()/CLOCKS_PER_SEC);
}
当然,你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间:
上面我们看到时钟计时单元的长度为1毫秒,那么计时的精度也为1毫秒,那么我们可不可以通过改变CLOCKS_PER_SEC的定义,通过把它定义的大一些,从而使计时精度更高呢?通过尝试,你会发现这样是不行的。在标准C/C++中,最小的计时单位是一毫秒。
(3)与日期和时间相关的数据结构
在标准C/C++中,我们可通过tm结构来获得日期和时间,tm结构在time.h中的定义如下:
struct tm {
int tm_sec; /* 秒 – 取值区间为[0,59] */
int tm_min; /* 分 - 取值区间为[0,59] */
int tm_hour; /* 时 - 取值区间为[0,23] */
int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
int tm_year; /* 年份,其值等于实际年份减去1900 */
int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
int tm_yday; /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
};
ANSI C标准称使用tm结构的这种时间表示为分解时间(broken-down time)。
而日历时间(Calendar Time)是通过time_t数据类型来表示的,用time_t表示的时间(日历时间)是从一个时间点(例如:1970年1月1日0时0分0秒)到此时的秒数。在time.h中,我们也可以看到time_t是一个长整型数:
大家可能会产生疑问:既然time_t实际上是长整型,到未来的某一天,从一个时间点(一般是1970年1月1日0时0分0秒)到那时的秒数(即日 历时间)超出了长整形所能表示的数的范围怎么办?对time_t数据类型的值来说,它所表示的时间不能晚于2038年1月18日19时14分07秒。为了 能够表示更久远的时间,一些编译器厂商引入了64位甚至更长的整形数来保存日历时间。比如微软在Visual C++中采用了__time64_t数据类型来保存日历时间,并通过_time64()函数来获得日历时间(而不是通过使用32位字的time()函 数),这样就可以通过该数据类型保存3001年1月1日0时0分0秒(不包括该时间点)之前的时间。
在time.h头文件中,我们还可以看到一些函数,它们都是以time_t为参数类型或返回值类型的函数:
double difftime(time_t time1, time_t time0);
time_t mktime(struct tm * timeptr);
time_t time(time_t * timer);
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);
此外,time.h还提供了两种不同的函数将日历时间(一个用time_t表示的整数)转换为我们平时看到的把年月日时分秒分开显示的时间格式tm:
struct tm * gmtime(const time_t *timer);
struct tm * localtime(const time_t * timer);
通过查阅MSDN,我们可以知道Microsoft C/C++ 7.0中时间点的值(time_t对象的值)是从1899年12月31日0时0分0秒到该时间点所经过的秒数,而其它各种版本的Microsoft C/C++和所有不同版本的Visual C++都是计算的从1970年1月1日0时0分0秒到该时间点所经过的秒数。
算法总结:
(一)判断一个数是不是素数
1. 一个数若可以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n),据此,代码并不需要遍历到n-1,遍历到sqrt(n)即可,因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数。
2.首先看一个关于质数分布的规律:大于等于5的质数一定和6的倍数相邻。例如5和7,11和13,17和19等等;
证明:令x≥1,将大于等于5的自然数表示如下:······ 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1 ······
可以看到,不在6的倍数两侧,即6x两侧的数为6x+2,6x+3,6x+4,由于2(3x+1),3(2x+1),2(3x+2) ,所以它们一定不是素数,再除去6x本身,显然,素数要出现只可能出现在6x的相邻两侧。这里要注意的一点是,在6的倍数相邻两侧并不是一定就是质数。
根据以上规律,判断质数可以6个为单元快进。
代码如下: if((num%6!=1)||(num%6!=5)) return 0;
int tem=sqrt(num);
for(i=5;i<=tem;i+=6)
if((num%i==0)||(num%(i+2)==0))
return 0; //排除所有
return 1;
(二)用牛顿迭代法求方程的根
牛顿迭代法:先任意设定一个与真实的根接近的Xk作为第一次近似根,由Xk求出f(Xk)。再过(Xk,f(Xk))点做f(x) 的切线,交x轴于Xk+1,它作为第二次近似根。给出误差范围即可。
由函数图像可得f'(Xk)=f(Xk)/(Xk-Xk+1),因此Xk+1=Xk-f(Xk)/f'(Xk);由Xk依次推出Xk+1,Xk+2,.......
例如f(x)=2x^3-4x^2+3x-6=((2x-4)x+3)-6,用后面的表达式可以节省时间。
代码如下:
do{
x0=x;
f=((2*x0-4)*x0+3)-6; /*邱发f(x0)*/
f1=(6*x0-8)*x0+3; /*求导数*/
x=x0-f/f1;
}while(fabs(x-x0)>=1e-5);
(三)排序算法
1.冒泡排序法
基本思路:将待排序的相邻数据元素两两比较,若逆序则交换。当从前向后依次扫描比较之后,最大的数会被放在最后一个位置,这个过程称为一趟排序。
第二趟扫描到倒数第二个数。
从前向后扫描,大数后移;从后向前扫描,小数前移。
N个数据,需N-1趟排序;第一趟排序,需要N-1次两两比较;第j趟排序,需要N-j次两两比较。
代码如下:
for(j=1;j<=N-1;j++)
for(i=0;i<N-j;i++)
if(a[i]>a[j])
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
改进算法:若某趟排序过程中没有元素交换,则说明已经达到有序状态。
2.选择排序法
基本思路:先在a[0]~a[N-1]中找到最小的元素,放在第一个位置;再从a[1]~a[N-1]找到最小的元素,放在第二个位置;共需进行N-1轮比较。
代码如下:for(i=0;i<N-1;i++)
{
k=i;
for(j=i+1;j<N;j++)
if(array[j]<array[k]) k=j; //用k记住所找的数中 最小的下标,避免了不必要的多次交换和比较
if(i!=k)
{
t=array[k];
array[k]=array[i];
array[i]=t;
}
}
(四)将数组元素逆过来,只能借助一个临时存储单元
方法:将前后的两个元素依次对调。
i=0,j=n-1; i=1,j=n-2; -->j=n-i-1
p=N/2-1;
(五)将字符串s转换成相应的双精度浮点数
库函数已经提供了功能,下面为思路;
首先对字符串中出现的'+'和'-'进行处理,其次处理'.'前的部分,即将每一个数字字符转换成相应的数值,并加权累加成相应的整数部分。
i=0;
sign=1;
if((s[i]=='+')||(s[i]=='-')) //sign处理符号
sign=(s[i++]=='+')?1:-1;
for(val=0;s[i]>='0'&&s[i]<='9';i++) //处理整数数字部分
val=10*val+s[i]-'0';
if(s[i]=='.')
i++;
for(power=1;s[i]>='0'&&s[i]<='9';i++) //处理小数数字部分
{
val=10*val+s[i]-'0';
power*=10;
}
number=sign*val/power;
(六)统计文本中的单词个数
对单词个数加1必须同时满足两个条件:第一,当前所检查的这个字符非空格;第二,所检查字符的前一个字符是空格。
代码如下:
prec=' ';num=0;i=0;
while(str[i]!='\0')
{
nowc=str[i];
if(nowc!=' '&&prec==' ') num++; //是新单词,个数加1
prec=nowc; //将已检查的字符保存至prec,继续检查
i++;
}
(七)找鞍点(行上最大,列上最小的元素)
如三行四列,代码如下:
find=0;i=0; //find为0,标志还未找到鞍点
while(i<3&&(find==0))
{
rmax=a[i][0];c=0;
for(j=1;j<4;j++)
if(rmax<a[i][j])
{
rmax=a[i][j]; c=j; //某行最大值所在列号c
}
find=1; k=0; //先假设行上的最大值为列上的最小值
while(k<3&&(find==1)) //内循环查rmax是否为c列上的最小数
{
if(k!=i)
if(a[k][c]<=rmax)
find=0;
k++;
}
}
(八)用计算机洗扑克牌
将54张牌编号为0,1,2,,,,,,,53。
存储54张扑克牌:数组PK,每个数组元素是一位三位数,第一位表示种类,后两位表示牌号。
将用到的库函数:rand()用于产生随机数,srand函数时随机数发生器的初始化函数,它需要提供一个种子,如srand(1)。
不过通常使用系统时间来初始化,即使用time函数来获得系统时间,它的返回值为从00:00:00 GMT ,January,1,1970到现在持续的秒数。
在进行抽取操作时,首先在0~53之间选择一个随机数r=rand()%53,将pk[0]与pk[r]交换;接着在1~53之间产生一个随机数r=rand()%(53-1)+1,将
pk[1]与pk[r]交换。。。即在i~53之间产生一个随机数r=rand()%(53-i)+i,将pk[i]与pk[r]交换,直到所有牌都被交换。
代码如下:
int pk[54]={501,502,
101,102,.................};
srand(time(0)); //用随机数设置随机数序列的
for(i=0;i<53;i++)
{
r=rand()%(54-i)+i; //产生i到53之间的随机数
temp=pk[i];
pk[i]=pk[r];
pk[r]=temp;
printf("%d",pk[i]);
}
(九)计算数组s中数组t出现的最右边位置
代码如下:
for(i=0;s[i]!='\0';i++)
{
for(j=i,k=0;t[k]!='\0'&&s[j]==s[k];j++,k++)
;
if(t[k]=='\0')
return i;
}
(一)标识符与关键字
1.32个关键字
auto,const,enum,extern,register,signed,sizeof,static,union,unsigned,void,volatile.
2.12个标识符作为编译预处理的命令单词,使用时前面加#
define,elif,else,endif,error,if,ifdef,ifndef,include,line,progma,undef
关键字或命令单词后必须有空格,圆括号,尖括号,双引号等分隔符,否则会与其他字符一起组成新的标识符
(二)数据类型
3.C语言的数据类型可分为基本类型(字符型,整型,实型--单双精度,无值型void),构造类型(枚举型enum,数组,结构体--含位段,共用体union),指针类型。
4.C语言数据类型的长度和值域
无值型(void)有两种用途:第一是明确地表示一个函数不返回任何值;第二是返回void*类型的指针,可指向任何类型的数据。
5.类型修饰符
signed 有符号 unsigned 无符号
long 长 short 短
整型数缺省状态为signed和short。
6.指针类型
动态分配内存,方便处理字符串,数组,调用函数时可得到“多于”一个返回值。
7.枚举型
“枚”是量词,相当于“个”,“举”是指将变量的值一一列举出来。实际上是用符号来表示若干个可取的整型值,它是整型的一个子集。
(三)常量
1.八进制整数以0作为开头
2.十六进制以0x为标志
3.一个二进制数0或1,成为位(bit)
4. 8个位称为一个字节(byte),每个字节存放在一个存储单元中,每个单元赋予一个存储地址。
5.用十进制数表示数据,十六进制数表示地址
6.单精度实型有小数和指数两种形式。
(1)小数形式:必须有小数点,如12.;实数仅有7位有效数字,超过7位的将是不精确的。如1.2345678将保存为1.234567,第八位将无法保留而失去,并不是四舍五入。当要求用五位小数表示时,则表达为1.23457,第七位向第六位四舍五入。
(2)指数形式:科学计数法。分成指数和尾数部分,尾数部分可以是整数形式或小数形式,指数部分是e后跟一个整数。由于实数仅有7位有效数字,因此在内存中用三个字节来表示尾数,用一个字节来表示指数,所以指数部分用两位整数来表示。如
-456.78e-01,4个字节。
7,双精度实型:有效位15~16位。双精度常量在内存中占8个字节。
(此处还有问题,为什么两个整数占一个字节)
8.字符常量
用单引号括起来的一个字符。以ASCII码形式存储在内存中。,每个字符在内存中占一个字节。
9.控制字符常量或转义字符常量:单引号内以“\”开头后跟转义字符,或八进制,十六进制数,它们是一类不可打印字符,代表某些功能。
10.字符串常量
用一对双引号括起来的字符。字符串常量由系统在字符序列最后加一个字符'\0'来表示。 \0表示空NULL;
(四)变量(先定义,后使用)
1.在同一层次的变量,不能与数组,指针,函数或其他变量同名。
2.字符型变量:仅占一个字节,其内存中存放的是该字符的ASCII。
3.枚举型变量:
(枚举型是整型常数的集合,这些常量指定了所有该类型变量可能具有的各种合法值)
enum <枚举类型名> {枚举元素表} <变量表>;
枚举类型名和变量表是选择项
例如:enum weekday {sun,mon,tue,wed,thu,fri,sat} ; weekday是枚举类型名,花括号内是该枚举类型变量可能具有的各种情况的一一列举;
enum weekday workday ,restday ; 定义枚举类型变量,即变量weekday,restday是属于enum weekday枚举类型的,该枚举型变量只能赋予花括号内的常量。
枚举类型的定义有三种形式:
(1)枚举类型和枚举变量分别定义;
(2)合并成一句;
(3)当只有一种枚举类型时,可省略枚举类型名;
花括号内的元素称为枚举元素或枚举常量,他们是用户定义的标识符,并不自动的代表什么含义。
枚举元素实际上是用它们所对应的整型数来代替,即枚举类型只是整型的一个子集,且可以在任何一个整型表达式中使用这些枚举值。具体的枚举元素所对应的整数由两种情况所决定:(1)缺省:当花括号内的枚举元素没有被初始化,第一项代表0,第二项代表1,以此类推。
(2)初始化:我们可以用初始化来改变枚举元素的相应值。
初值可以从任何一个整数开始,也可以指定几个初值,也可以给任何一个枚举常量赋初值。
枚举型变量值在输出时是输出其整常数而不是其枚举元素的标识符。枚举型变量在赋值时可以赋枚举元素而不能直接赋整型常量,如要赋整型常量,则要进行类型转换。
如:restday = (enum weekday) 6; 或 restday = sat;
4.变量的初始化
(1)全程和静态变量在程序编译阶段初始化,且只赋一次值。而局部变量是在进入定义它们的函数或复合语句时才做初始化,相当于赋值语句。每调用一次,就赋值一次。
(2)所有的全程和静态变量在没有明确初始化的情况下由程序自动赋0.而局部变量和寄存器变量在未初始化时其值是不确定的,即保持原来的状态不变。
(五)运算符
1.求字节数运算符(sizeof)
2.++,--用于整型和指针变量(取下一地址)
3.关系运算符的运算结果是逻辑量,0和非0.
4.在逻辑表达式的求解过程中,并不是所有的逻辑运算符都被执行,只是在必须执行下一个逻辑运算符才能求出表达式的值时,才执行该运算符。(执行与否会影响后续变量的值)
5.位运算符:位运算是对字节或字(char,int等)中的实际二进制位进行检测,设置或移位。位运算不能用于float,double,void或其它更复杂的数据结构。
&与 ^异或 |或 ~反 <<左移 >>右移
6.赋值语句从右向左结合。先计算右边表达式的值,再转换成表达式左边变量的类型,再进行赋值。
7.表达式中的类型转换:当不同类型的常量和变量混合使用时,它们最终会转换成同一类型,向上靠。不同类型数据计算前自动转换成相同的类型,再进行计算。
(1)转换结果必定是int,long,double.
(2)各类型级别由低到高的顺序为char,int,unsigned,long,unsigned long,float,double.有符号和无符号数进行混合运算,结果为无符号类型。
例:float x; int i; x = i = 3.14159
i = 3; x = 3.0
二.C程序设计初步
(一)结构化程序设计思想
(二)C语句概述
1.空语句仅有一个分号,常用语控制语句必须出现语句之处,不做任何操作,只在逻辑上起到一个语句的作用。
(三)赋值语句
(四)数据输出
把数据送到stdout标准输出流。
几个标准输出库函数:
1.putchar(ch);单个字符输出
ch可以是整型变量,此时仅输出低字节所代表的字符。
2.printf()格式化输出函数
输出八进制整数 %o 十六进制整数 %x %p:显示一个指针地址
(1)%md 最小宽度为m位,一般右对齐,少于m位左补空格或0,数据要左补0,则在m前面加个0. %0md
(2)%m.nf,m为总宽度(包括小数点),n为小数部分位数,若小数部分位数超过n位,则n+1位向n位四舍五入,整个数据小于m位左补空。
%m.ne 小数部分n位(包括e在内),全部长度取m位。
(3)%m.ns m为总长度,n表示只取字符串左端的n位。n<m时,左端补空格。
(4)-表示左对齐格式。
(5)l 在输出d,i,o,u,x等整型量时,在其前面加上l表示输出的是长整型数;在e,f,g等实型量前加l表示输出的是一个双精度实型数。
(6)一个整型数可以用不同格式来输出。
(7)一个实数可以按%f和%e来输出,如果按%f来输出,则能输出全部整数,并保留6位小数,但有效位只有前5位。如果按%e输出,则系统自动给出6位小数,小数点前有一位非零整数,后跟一个e,指数的正负符号位占一位,数值部分占2位。
如123.456 %f 123.455994 六位小数,但有效位只有前五位(包括整数部分但不包括小数点)
%e 1.23456e+02 尾数总共只有六位,小数点前一位非零的。
(8)无符号十进制数的输出 %u -1 --> 65535 (尚不明确) -2 ==》65534
3.puts()函数(字符串输出函数)
char * puts(char *s);
puts()函数将字符串数据写在屏幕上并换行。它仅用来输出一字符串,不能输出数值也不能进行格式变换,也可以输出转义字符。
(五)数据输入
1.getche()与getchar(),getch()
ch = getche(); 等待从键盘输入一个字符,返回它的值并在屏幕上自动回显该字符,必须使用<conio.h>。(即输入时回显)
getchar():它的输入缓冲区一直到键入一个回车符才返回给系统,这样就可能在getchar()返回之后还留下一些字符在输入排队流中。(建议不用)
getch():不把读入的字符回显,避免不必要的显示。<conio.h>
2.scanf()函数:输入具有某种格式的数据
(1)在格式控制字符串中的一个空白字符会使scanf()函数在读操作中略去输入流中的一个或多个空白字符。输入数据必须用空格,制表符或回车来分隔。
(2)格式说明还可以带修饰项,如确定输入最大位数,可少于,当多于时只读入该数所表示的位数,多余数据将作为下一个数据读入其他变量。
(3)一个格式说明符中出现“*”修饰符时,表示读入一个该类型的数据并不存储(即跳过该数据)
(4)%s读入字符串时,参量表内必须是字符数组名或字符指针,在使用字符数组名或指针名时,前面不必加&,因为数组名是数组存储的起始地址,指针内存放的也是地址。
(5)数据输入时不能规定精度。
3.gets()字符串输入函数
必须用回车作为数据输入结束,该回车符并不属于这串字符,由一个空操作符(\0)在串的最后来代替它。此时空格不能结束字符串的输入,返回一个指针。
三.分支结构的C程序设计
(一)分支结构中的表达式
1.C语言中的逻辑值:非零为真,只有0和'\0'(其ASCII值为0)为假
2.关系表达式的运算结果:1为真,0为假
3.闰年:能被4整除但不能被100整除,或能被400整除
(二)if语句
1.简单形式:if( 表达式 ) 语句;
2.if~else结构:
3.条件运算符中的语句可以是函数。
4.嵌套最多可达15层
5.if else if else if ...... else
(三)switch语句
switch(表达式) #表达式可以是整型,字符型,枚举型
{
case 常量一: 语句段1;[break]
default:语句段n
}
计算表达式的值,同case后的常量比较,相等则执行后面的语句。如果没有break,则执行后面所有的语句。
如果没有default,匹配不成功时什么也不执行。
每个case分支可有多条语句,但不必用{}。最多可有257个常量。
当若干分支需要执行相同操作时,可利用空语句,将几个case分支写在一起。
case后面的常量必须是字符常量。
五.循环结构的C程序设计
(一)while语句
(二)do-while语句
(三)for语句中的任意一个表达式都可以省略,但封号一定要保留。
(四)break语句:只能用于switch语句或循环结构。只能跳出它所在的循环,不能跳出多层循环。
continue:结束本次循环,即跳过循环体中尚未执行的语句,直接进行下一次是否执行循环的判定。
六.数组
(一)一维数组
1.C语言不允许定义动态数组,即数组的长度不能依赖于程序运行过程中变化着的量。因为C语言是在编译阶段为数组开辟单元,而运行时才能得到的变量值远晚于编译阶段,是无法实现的。
2.先定义后使用,只能逐个引用数组元素,不能一次引用整个数组。
3.C编译系统对数组下标越界并不给出错误提示。
4.一维数组初始化:
int s[5]={67,37,90},则后两个数值为0.若对全部数组元素赋初值,可以不指定数组长度。 int s[]={1,2,3,4,5};
数组占有连续的存储单元。
(二)二维数组
1.int a[3][2] 三行两列 ,二维数组的排列顺序是按行存放的,即在内存中,先顺序存放第一行,再顺序存放第二行。
2.二维数组初始化:可以用分行赋值:int a[3][2]={{1,2},{2,3},{3,4}};部分元素赋初值时,其余元素自动赋0.
也可以将所有数据写在一个花括号内,按数组的排列顺序对各元素赋初值。
若对全部元素都赋初值,则定义数组时第一维度的长度可以不确定,但第二维度的长度不能省。
若没有初始化,则定义数组时,所有维的长度都必须给出。
(三)字符数组与字符串
1.内存中存放的是用整数表示的该字符的ASCII码。
2.只有字符串常量的概念,没有字符串变量的概念。用字符数组来处理字符串。
3.字符串常量是用双引号括起来的字符序列。在数组中末尾存放一个字符'\0'(ASCII为0),用它来作为字符串结束的标志;
4.串的长度不包括'\0';定义数组存放字符串时应保证数组长度大于将存放的字符串长度。
5. 字符串初始化:char c[]={"string"};或char c[]="string";或c[]={'s',........,'\0'};
(四)常用字符串处理函数
1.gets字符串输入函数:gets(字符数组)
从标准输入文件中读取一个字符串到字符数组中。它读取字符串直到遇到换行符'\n',并将换行符转换为字符结束输入标志符'\0'存放到字符数组中。
将空格也作为元素读入,不同于scanf
2.puts字符串输出函数:在输出时将'\0'转换为换行符,即输出字符后换行。
3. scanf字符串的输入是以“空格”,“Tab”或“回车”来结束输入。同时输入多个字符串时,字符串之间以“空格”为间隔,最后按“回车”结束输入
七.函数及变量存储类型
(一)函数基础与C程序结构
1.C程序的结构化设计思想
将一个复杂的任务划分为若干子任务,每个子任务设计成一个子程序,成为模块。
C语言是函数式语言,没有子程序,利用函数来实施结构化设计程序。
组成一个C程序的各函数可以分开编辑成多个C源文件。一个C源文件有0个或多个函数。
2.函数概述:c程序总是从main函数开始执行。
函数声明:指出函数原型,包括函数名,参数和返回值类型。
(二)函数的定义和声明
1.函数定义的一般形式:存储类型标识符 类型标识符 函数名(形参及类型说明)
存储类型标识符说明函数的存储类型,它规定了函数可被调用的范围。可用于函数的存储类型标识符有static和extern,指定为static的函数为静态函数,
静态函数只能由和它在同一文件中定义的函数调用;不指定存储类型标识符时为缺省状态的存储类型extern,为外部函数。
2.不能定义返回数组的函数。
3.外部函数的名字要作用于整个程序,因而外部函数相互之间不能重名。静态函数可以和外部函数同名,但同一文件中的函数不能同名。
4.return 表达式;或return (表达式);
5.对于基本类型,表达式的类型和函数的类型不相同时表达式的值自动转换为函数的类型。对于指针,须使用类型强制符将表达式的值转换为函数的类型;对于结构体,表达式值的类型与函数定义的类型必须相同。
6.C语言允许函数先调用后定义,或被调用函数在其它文件中定义。
7.非int函数,必须在调用函数之前作函数声明,其目的是指出被调用函数的类型和参数类型,否则编译程序认为被调用函数为int类型。(最新C++库不再默认为int)
函数声明一般格式:存储类型标识符 类型标识符 函数名(形参表);
外部函数声明可指定extern或存储类型标识符缺省,静态函数声明必须指定static;参数表可以只列出参数的类型名而不需给出参数名。声明时给出的参数名被编译忽略,因为参数的存储分配是在函数被调用时进行的。
8.函数声明可位于调用函数体内或函数体外。在函数体外声明的函数可在声明之后直至该源文件结束的任何函数中调用,在函数体内声明的函数只能在声明所在的函数体内调用。
(三)函数的调用
1.函数调用在程序中起一个表达式或语句的作用。
2.对于无返回值函数的调用,只能以语句形式出现。
3.被调用函数的定义出现在主调函数之前。
4.形参与实参的数值传递
C语言中,参数的传递方式是“单向值传递”,形参和实参变量各自有不同的存储单元,被调用函数中形参变量值的变化不会影响实参变量的变化。
例如:
void swap(int x,int y)
{
int z;
z=x;x=y;y=z;
}
int main()
{
a=10;b=20;
swap(a,b);
}
a=10,b=20;
原因:a=10 --> x=10 ; b=20 --> y=20 ; ------ 结果 a=10 -->x=20 ; b=20 --> y=10
5.C语言中可以定义参数数目可变的函数。至少要给出一个形参,后面列三个点。
6.多数编译程序在计算参数值时按从右到左的顺序。
7.如果函数的类型不是void,既是函数没有return语句,函数也有返回值,只是返回值为不确定的值。
(四).变量的存储类别
1.C语言的变量有两种属性:数据类型和存储类型。因此完整的变量说明的一般形式为:存储类型标识符 类型标识符 变量名;
2.C语言的四种存储类型:auto(自动),extern(全局),static(静态),register(寄存器)。
3.动态存储和静态存储
内存中供用户使用的存储空间可分为程序区,动态存储区和静态存储区。
程序区用来存放程序代码,动态和静态存储区用来存放数据,即数据与处理数据的程序是分离的。
静态存储区即全局数据区,存放全局数据和静态数据。
动态存储区可分为堆区和栈区。堆区用来存放程序的动态数据,栈区用来存放程序局部数据,即各个函数中的数据。
动态存储区生存期为程序运行的某个阶段,静态存储区数据为整个程序运行过程。
4.局部变量
局部变量的存储类型可以通过类型标识符auto和static来规定。
auto--动态存储区--可以缺省;static--静态存储区。
编译器并不将局部自动变量预置位0;
局部变量还可以与全局变量同名,此时在局部变量的作用域内,全局变量不起作用。
5.局部静态变量的使用
在函数调用结束后,该变量虽然仍在内存中存在。局部静态变量可以用来优化程序。
6.全局变量
全局变量是在所有函数之外定义的,静态存储区中。
通过extern作引用说明,全局变量的作用域可以扩大到整个程序的所有文件。
用static,与普通全局变量的区别是它的作用域。普通全局变量对文件中的所有函数可见,而且还能被其它文件中的函数所用;static型的仅对其所在文件定义后的函数可见,不能被其它文件使用。
全局变量初始化只执行一次,若无显式初始化,则由系统自动初始化为与变量类型相同的0初值。
7.寄存器变量
(五).编译预处理
1.宏定义
#define 名字 替换文本
(1)不带参数的宏定义:#define 标识符 字符串 一般替换文本是#define指令所在行的剩余部分,但也可以为若干行,在待续行后加反斜杠\即可。
宏定义不是C语句,不加分号;可以用#undef命令终止宏定义的作用域。
可以用已定义的宏名,并层层替换;对程序中用双引号括起来的字符,不进行替换。
(2)带参数的宏定义:不仅是简单的字符串替换,还要进行参数替换。 #define 宏名(参数表) 字符串 如:#define S(a,b) a*b
宏名与带参的括号之间不应加空格,否则将空格以后的字符串都作为代替字符串的一部分
定义宏时,最好将参数和宏体用括号括起来。 如:square(n) n*n s=square(a+1) s-a+1*a+1,与预期效果不同
2.文件包含处理
一个文件可以将另一个文件的全部内容包含进来。
一个include只能包含一个文件;1包含2,2要用三,则在1中用两次include,而且3要 在2之前。
文件包含是可以嵌套的;
#include命令中,使用双引号,系统首先在引用被包含文件的源文件所在目录中查找要包含的文件,若未找到,再按系统指定的标准方式检索其它目录;
用尖括号时,不检查源文件所在的目录而直接按照系统标准方式检索目录文件。
一般用双引号比较保险。
一般地,将系统提供的库文件采用尖括号表示,而用户自定义的文件采用双引号。
3.条件编译
对一部分内容指定编译的条件;
(1)#ifdef 标识符
程序段1
#else
程序段2
#endif
如果标识符已被#define命令定义过,则对程序段1进行编译;否则对程序段2编译;
(2)#ifndef 标识符
程序段1
#else
程序段2
#endif
如果标识符未被定义,则对程序段1进行编译。。。。。。。。。。
(3)#if 常量表达式
程序段1
#else
程序段2
#endif
指定表达式非0,就编译程序1;
八.指针
(一)指针的概念与定义
1.指针就是用来存放地址的变量。某个指针存放了哪个变量的地址,就说该指针指向了这个变量。
2.变量的地址:&变量名
3.存放变量地址的变量称为指针;被pc指向的变量c称为pc的对象;对象就是一个有名字的内存区域,即一个变量。
4.指针的定义:类型名 *指针名;
5.指针要有明确的当前指向,通过初始化或赋值来完成。
6.*:指针运算符(或称“间接访问运算符”)
7.&和*二者的优先级相同,结合方向为自右至左。
(二)指针作函数参数
a=10,b=20;
void swap(int *pa,int *pb)
{
int temp;
temp = *pa; *pa=*pb; *pb=temp;
}
形参的改变无法返回给实参,但利用形参指针对其所指向单元内容的操作,就有可能改变主调函数中变量的值。
(三)指针与数组
1.数组名成为符号常量,其值为数组在内存中所占用单元的首地址;即数组名代表了数组的首地址。
指针存放数组中第一个元素的地址时,该指针指向了这个数组。
2.pa=&a[0]或pa=a;
3.指向数组的指针加1等效于数组元素下标加1.
4.C允许:pa[i]和*(a+i),等价于*(pa+i)和a[i].
5.a=pa;a++;pa=&a都是非法的。
6.数组作函数参数
将数组的首地址作为函数参数传递时,有两种方式:指向数组的指针,和数组名;
形参和实参都是指针时,实参存放的是某个数组的首地址,并把这个地址传递给形参。
实参是数组名,形参是指针时,同类型的常量实参传递给变量形参,此时,对形参指针所指向的内容的访问就是对数组的访问。
形参和实参都是数组时名,虽然有两个数组名,但是只有一个数组,C不会给形参数组再开辟一个内存单元,而是认为形参数组名是实参数组名的别名,也就是说,对形参数组的操作就是对实参数组的操作。
实参是指针,形参是数组名,系统认为实参指针是指向某个数组的,此时形参数组与实参所指向的数组是同一数组,且为该数组的别名。
for(i=0;i<10;i++)
scanf("%d",p++)
x是指针,用它访问数组元素时可以用下标法,也可以用指针法。
指针与数组的几种等价:a[i] --> *(pa+i) --> *(a+i) -->pa[i] a==pa
7.指针和字符串
char *point="I am a stu";
char *point; point="I am a stu"; 字符串常量的值就是该字符串在内存中的首地址。
char string[]=""; char *point=""; 第一个为常量,不能赋值;第二个可以赋值。
字符串拷贝函数:
void my_strcpy(char *t,char *s)
{
while((*t=*s)!='\0')
{
s++;
t++;
}
}
8.指向多维数组的指针
a[2][4] 数组a看成一维数组,包含两个元素;
a[0]和a[1]虽然没有显式的定义,但它们可以被认为是数组名,是数组在内存中的首地址,这一点与数组名a一样,与a不同的是类型。
表示形式 类型 含义
a,&a[0] 行地址 第0行地址
a+1,&a[1] 行地址 第1行地址
a[0],*a 列地址 第0行第0列地址
a[1],*(a+1) 列地址 第1行第0列的地址
a[1]+2,*(a+1)+2,&a[1][2] 列地址 第1行第2列的地址
a[1][2],*(a[1]+2),*(*(a+1)+2) 整形 第1行第2列元素
对行地址进行一次指针运算就成为列地址,而对列地址进行一次取地址运算就成为行地址。
定义行指针: 类型名 (*指针名)[数组长度];(两个约束条件:一是所指向数组的类型,二是每行的行数)
*(*(p+i)+j) p[i][j] (*(p+i))[j]
多维数组作函数参数的问题:
(1)形参说明为指向数组元素的指针,实参为数组元素的地址或指向元素的指针。
(2)形参说明为行指针,实参为行地址或行指针。
9.指针数组
由指针变量组成的数组称为指针数组。
类型名 *数组名[常量表达式];
指针数组的主要用途是表示二维数组,尤其是表示字符串的数组。 用指针数组表示二维数组的优点是:每一个字符串可以具有不同的长度。
如: int *pa[4],a[4][4];
pa[0] = &a[0][0]或a[0];
pa[1] = &a[1][0]或a[1];
引用a[i][0],*pa[i],*(*(pa+i)+0),*(pa[i]+0).
(四)指针与函数
1.指针指向函数,即指针存放函数的入口地址;返回值为指针。
2.指向函数的指针
(1)通过指针来访问函数
类型标识符 (*指针名)(); 定义指向标识符类型的函数。
一个函数的入口地址由函数名表示,它是函数体内第一个可执行语句的代码在内存中的地址。
把函数名赋给一个指向函数的指针,就可以用该函数型指针来调用函数。
C允许将一个函数名作为参数传递给另一个函数。
如定义三个函数 int max(),min(),sun();
void operate(int x,int y,int (*fun)())
3.返回指针的函数
C的函数可以返回除数组。共用体变量和函数以外的任何类型数据和指向任何类型的指针。
注意:指针函数与指向函数的指针的区别。后者:(*a)();
(五)复杂指针
1.指向指针的指针
char **p_p;(即*(*p_p)
int a[10],*p; 可以认为a和p的类型相同。
int *aa[10],**p_p; 可以认为aa和p_p类型相同。
2.命令行参数
在支持C语言的环境中, 当程序开始运行时可以将命令行参数传递给它。运行程序相当于调用main()函数。
main()可以有两个参数:第一个(通常命名为argc)是程序所调用的命令行参数的个数;第二个(通常命名为argv)是一个指针数组,数组
中的每个元素指向命令行的每个字符串。
如:echo程序 int main(int argc,char *argv[])
程序运行命令行情况:echo hello world回车
argv[0]为调用程序的名字,所以argc至少为1.
一般情况下要求argv[argc]为空指针。
利用命令行参数来确定程序的操作或流程。
3.复杂指针的理解
注意事项:
(1)数组的元素不能为函数(可以为函数的指针)。
(2)函数的返回值不能为数组或函数(可以为函数或数组的指针)。
理解复杂说明符:从标识符开始,按照运算符的优先级和结合性顺序逐步解释。
九.结构体和共用体
(一)结构体
1.构造类型与基本类型的本质区别是它们不是全新的数据类型,而只是对基本类型的“封装”,由基本类型“构造”而来。
2.结构体类型的定义:自己先定义结构体类型(即结构体的组成)
关键字和类型名字组合成一种新的类型标识符,类似int。
3.结构体型变量的定义
结构体类型的定义本身不会创建任何变量,它只是一种模板,限定了这种类型的结构体变量的组成样式。
在编译时,结构体类型并不分配空间,只对结构体类型的变量分配空间。一般占用一片连续的存储空间。
4.结构体型变量及其成员的引用
它是一种聚合性变量,可访问的对象有结构体型变量名本身和成员名。
(1)访问结构成员:结构体变量名.成员名 &(work.cost)等于&work.cost
(2)相同结构体类型的结构体可以通过整体引用来赋值,但不允许对结构体型变量进行任何逻辑运算。如果要比较,只能逐个成员比较。
也不可以对结构体型变量进行整体的输入/输出,只能逐个输入输出。
(3)结构体型变量占据的一片存储单元的首地址称为该结构体型变量的地址,其每个成员占据的若干单元的首地址称为该成员的地址,两个地址均可用。
结构体型变量的地址主要用作函数参数。
5.结构体型变量的初始化:struct staff worker1={"wangwei",1200,13};
未初始化的成员不赋0.
(二)嵌套结构
1.允许一个结构体包含另一个结构体或者把一个结构体成员定义为数组。
2.内部结构体类型初始化时用花括号括起来。
(三)结构体型数组
1.结构体型数组的初始化:每个元素的初值由{}括起来。
2.“.”优先于&和*。
(四)结构体型指针
1.用指针访问结构体成员: (*指针名).成员名
指向成员运算符:-> 指针名->成员名 ->优先于++
(五)结构体与函数
1.结构体作函数参数
结构体型变量,变量的成员,指针都可作为参数。
单向传递,即处理结果是带不回来的。
2.结构体作为函数返回值
函数在处理结构体类型时,可以将其模块化为简单类型来计算结果。
(六)内存的动态分配
1.开辟和释放内存区的函数
malloc函数 void *malloc(unsigned size) 在内存的动态区分配长度为size个字节的连续空间。
分配成功,返回分配空间的起始地址;否则返回空指针。 强制类型转换
free(p)函数 释放由p指向的内存区
void *calloc(unsigned n,unsigned size) 分配n个大小为size个字节的连续空间,用于动态数组的分配
void *realloc(void *p,unsigned size) 将p所指出的已分配的内存空间重新分配成大小为size个字节的空间。用于改变已分配空间的大小,可以增减单元数。
(七)共用体(联合)
1.关键词union 标识符{} 共用一段内存
2.共用体不能整体引用。 引用.
3.给一个新的成员赋值就冲掉了原有成员的值,每个瞬间都只有一个变量起作用。
4.共用体型变量的地址和它的各成员的地址同值,&data,&data.i,&data.ch.
5.不能在定义共用体型变量时对其初始化。 不能作为函数参数或函数返回值,但可以使用指向共用体型变量的指针。
(八)位段
1.C语言的特点之一是可以代替汇编语言编写系统程序。
2.位段是一种特殊的结构体类型,其每个成员是以位为单位来定义长度的,不再是各种类型的变量。
例如:struct packed_data{ unsigned x:1; unsigned y:2; unsigned z:3;}bits;
该结构体定义了一个长度为1位,2位,3位的位段,1,2,3,说明了相应成员所占存储单元中二进制的位数。
3.一个位段必须被说明成int,unsigned,signed中的任一种,长度为1的位段被认为是unsigned类型,因为单个位不可能具有符号。
4.位段中成员的引用与结构体引用相同,“.”。
5.可定义无名位段: struct{ unsigned a:1; unsigned b:2; unsigned :5;//此5位无名,不用 unsigned c:8;};
6.一个位段必须存储在同一存储单元中,不能跨两个单元,所以位段总长不能超过整型长度的边界。
7.位段不能定义成数组。
8.位段可在表达式中被引用(按整型数),也可以用整型格式符输出。
(九)类型定义
1.允许用户用类型定义将已有的类型标识符定义成新的类型标识符,新的类型标识符可当做原标识符使用。
2.typedef typename identifier; 如:typedef int A; A i;
3.用typedef还可以定义其它各种已经定义过的类型。
(1)数组: typedef char STRING[80]; STRING s1,s2; <==> char s1[80],s2[80];
(2)指针: typedef float *PFLOAT; PFLOAT p1,p2; <==> float *p1,*p2;
(3) 函数: typedef char FCH() FCH af; <==> char af();
typedef FCH *PFCH; PFCH bf; <==> char (*bf)();
4.不能直接定义变量,可以嵌套定义。
十.文件
(一).文件的概念
1.根据文件内数据的组织形式,文件可分为文本文件(ASCII文件)和二进制文件。
2.源程序文件.C,经编译后得到的目标文件.OBJ,连接之后形成的可执行文件.EXE.
3.在头文件stdio.h中,定义了一个名为FILE的类型,包含了所有与文件操作有关的数据成员。有了FILE类型后,可以定义文件型指针。
4.文件由磁盘文件和设备文件组成。磁盘文件之一为数据文件。 一切能进行输入/输出的终端设备-->设备文件 如键盘为标准输入文件
(二)文件的打开与关闭
1.文件的打开 fopen()
FILE *fp; fp=fopen(文件名,使用文件方式);
文件使用方式 含义
"r"(只读) 为输入打开一个文本文件
"w"(只写) 为输出打开一个文本文件 若原来文件不存在,则在打开时建立一个按指定名字命名的文件。如果原来已存在一个以该文件名命名的文件,则在打开时将该文件删去,然后重新建立一个新文件。
"a"(追加) 向文本文件尾增加数据 文件必须存在
"rb"(只读) 为输入打开一个二进制文件
"wb"(只写) 为输出打开一个二进制文件
"ab"(追加) 向二进制文件尾增加数据
"r+"(读/写) 为读/写打开一个文本文件
"w+"(读/写) 为读/写建立一个新的文本文件
"a+"(读/写) 为读/写打开一个文本文件
"rb+"(读/写) 为读/写打开一个二进制文件
"wb+"(读/写) 为读/写建立一个新的二进制文件
"ab+"(读/写) 打开一个二进制文件,允许读,或在文件末追加数据
2.常用下面的程序打开一个文件
if((fp=fopen("file1","r"))==NULL)
{
printf("Can not open this file\n");
exit(0); /关闭所有文件,终止正执行的程序
} //先检查打开操作是否正确
3.在用文本文件向计算机输入数据时,将回车换行符转换为一个换行符,在输出时把换行符转换成回车和换行两个字符。在用二进制文件时,不进行这种转换,内存中的数据形式与输出到外部文件中的数据形式完全一致,一一对应。
4.文件的关闭 fclose()
fclose(文件指针).
如果程序终止之前不关闭文件,将可能丢失缓冲区最后一批未处理的数据,因为fclose()的调用不仅释放文件指针,还刷新缓冲区。
程序结束时会自动关闭文件。
fclose()函数也返回一个值:0表示关闭成功,EOF则表示出错。
(三)文件的读/写
1.fputc()函数和fgetc()函数
fputc(ch,fp);将字符(ch的值)输出到fp所指向的文件中。 输出成功,返回输出的字符;失败则返回EOF(-1)
ch=fgetc(fp); 从指定文件读入一个字符,该文件必须是以读或读/写方式打开的。如果在执行过程中遇到文件结束符,函数则返回一个文件结束标志EOF,可以利用它来判断是否读完了文件中的数据。
如果想从一个磁盘文件按顺序读入字符并在屏幕上显示出来,可编程为
while((ch=fgetc(fp))!=EOF)
putchar(ch); //不再适用
feof()函数来判断文件是否真的结束。 feop(fp)用来测试fp指向的文件当前状态是否为“文件结束”,是返回1,否返回0.
2.fgets()函数与fputs()函数
fgets(str,n,fp);从fp指向的文件读入n-1个字符,并把放到字符数组str(也可以是字符指针)中,如果在读入n-1个字符结束之前遇到换行符或EOF,读入即结束。
字符串读入在最后加一个'\0',fgets()返回值为str的首地址。
fputs(str,fp);将字符串输出到指定的文件。若调用成功,返回0,否则返回非0值。
3.fprintf()函数和fscanf()函数
fprintf(文件指针,控制字符串,参量表);
fscanf(文件指针,控制字符串,参量表);
4.fread()函数和fwrite()函数 --结构体型变量
fread(buf,size,n,fp);
fwrite(buf,size,n,fp);
buf是一个指针,它是读入数据将要存放的起始地址,或输出数据的起始地址。
size是要读/写的一个数据项的字节数。
n是要进行读/写数据项的个数。
调用成功,则函数返回为n的值;否则返回一个不足的计数。
(四)文件的定位
1.文件中有一个位置指针指向下一个要读的数据,成为当前工作指针。每读/写完一个数据,该位置指针自动向后移动一个数据的位置,这就是顺序读/写。
2.rewind()函数
强制使当前工作指针指向文件的开头。一般在要重新从头读/写文件时使用。rewind(fp);
3.fseek()函数
fseek(文件类型指针,位移量,起始点);
控制文件位置的指针进行随机读写。
起始点 常量表示 数字表示
文件首 SEEK_SET 0
当前位置 SEEK_CUR 1
文件尾 SEEK_END 2
位移量指从起始点向前移动的字节数。
fseek()函数一般用于二进制文件,因为文本文件要发生字符转换,计算位置时容易发生混乱。
4.ftell()函数
得到流式文件中位置指针的当前位置,用相对于文件开头的位移量表示。
若返回-1L,表示函数调用出错。
库文件
(一)<math.h>
1.int abs(int x) 求整数x的绝对值
2.double fabs(double x) 求双精度实数x的绝对值
3.double acos(double x) 计算cos^(-1)(x)的值 x在-1~1范围内
4.double asin(double x) 计算sin-1(x)的值 x在-1~1范围内
5.double atan(double x) 计算tan-1(x)的值
6.double atan2(double x) 计算tan-1(x/y)的值
7.double cos(double x) 计算cos(x)的值 x的单位为弧度
8.double cosh(double x) 计算双曲余弦cosh(x)的值
9.double exp(double x) 求ex的值
10.double floor(double x) 求不大于双精度实数x的最大整数
11.double fmod(double x,double y) 求x/y整除后的双精度余数
12.double frexp(double val,int *exp) 把双精度val分解尾数和以2为底的指数n,即val=x*2n,n存放在exp所指的变量中,返回位数x 0.5≤x<1
13.double log(double x) 求㏑x
14.double log10(double x) 求log10x
15.double modf(double val,double *ip) 把双精度val分解成整数部分和小数部分,整数部分存放在ip所指的变量中,返回小数部分
16.double pow(double x,double y) 计算x^y的值
17.double sin(double x) 计算sin(x)的值
18.double sinh(double x) 计算x的双曲正弦函数sinh(x)的值
19.double sqrt(double x) 计算x的开方
20.double tan(double x) 计算tan(x)
21.double tanh(double x) 计算x的双曲正切函数tanh(x)的值
(二)<string.h>
1.strcmp字符串比较函数 strcmp(字符1,字符2)
2.strcpy字符串拷贝: strcpy(字符数组1,字符2,n),将字符2的前n个字符拷贝到数组1中,字符数组1必须足够大。
3.strcat字符串连接函数:字符2连接到字符1的后面,结果保存到第一个字符中。
4.strlen字符串长度,不包括'\0';
5.strlwr:大写字母变小写
6.strupr:小写字母变大写
(三)<stdlib.h>动态分配和随机函数
1.void *free(void *p) 释放p所指的内存区
2.void *malloc(unsigned size) 分配size个字节的存储空间
4.void exit(int state) 程序终止执行,返回调用过程,state为0正常终止,非0非正常终止
5.void *calloc(unsigned n,unsigned int size) 分配n个数据项的连续内存空间,每个数据项的大小为size字节
6.void **realloc(void *p,unsigned int newsize) 把p所指内存区的大小改为size个字节
7.int rand(void) 产生随机数
8.void srand(unsigned seed) 初始化随机数发生器,srand()以给定数进行初始化。
(四)<time.h>
(1).概念
1.Coordinated Universal Time(UTC):协调世界时,又称为世界标准时间,也就是大家所熟知的格林威治标准时间(Greenwich Mean Time,GMT)。比如,中国内地的时间与UTC的时差为+8,也就是UTC+8。美国是UTC-5。
2.Calendar Time:日历时间,是用“从一个标准时间点到此时的时间经过的秒数”来表示的时间。这个标准时间点对不同的编译器来说会有所不同,但对一个编译系统来 说,这个标准时间点是不变的,该编译系统中的时间对应的日历时间都通过该标准时间点来衡量,所以可以说日历时间是“相对时间”,但是无论你在哪一个时区, 在同一时刻对同一个标准时间点来说,日历时间都是一样的。
3.epoch:时间点。时间点在标准C/C++中是一个整数,它用此时的时间和标准时间点相差的秒数(即日历时间)来表示。
4.clock tick:时钟计时单元(而不把它叫做时钟滴答次数),一个时钟计时单元的时间长短是由CPU控制的。一个clock tick不是CPU的一个时钟周期,而是C/C++的一个基本计时单位。
(2).计时
C/C++中的计时函数是clock(),而与其相关的数据类型是clock_t
clock函数定义如下: clock_t clock( void );
这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,在MSDN中称之为挂钟时间(wal-clock)。其中clock_t是用来保存时间的数据类型, clock_t是一个长整形数。在time.h文件中,还定义了一个常量CLOCKS_PER_SEC,它用来表示一秒钟会有多少个时钟计时单元,其定义如下:
可以看到每过千分之一秒(1毫秒),调用clock()函数返回的值就加1。
下面举个例子,你可以使用公式clock()/CLOCKS_PER_SEC来计算一个进程自身的运行时间:
void elapsed_time()
{
printf("Elapsed time:%u secs./n",clock()/CLOCKS_PER_SEC);
}
当然,你也可以用clock函数来计算你的机器运行一个循环或者处理其它事件到底花了多少时间:
上面我们看到时钟计时单元的长度为1毫秒,那么计时的精度也为1毫秒,那么我们可不可以通过改变CLOCKS_PER_SEC的定义,通过把它定义的大一些,从而使计时精度更高呢?通过尝试,你会发现这样是不行的。在标准C/C++中,最小的计时单位是一毫秒。
(3)与日期和时间相关的数据结构
在标准C/C++中,我们可通过tm结构来获得日期和时间,tm结构在time.h中的定义如下:
struct tm {
int tm_sec; /* 秒 – 取值区间为[0,59] */
int tm_min; /* 分 - 取值区间为[0,59] */
int tm_hour; /* 时 - 取值区间为[0,23] */
int tm_mday; /* 一个月中的日期 - 取值区间为[1,31] */
int tm_mon; /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */
int tm_year; /* 年份,其值等于实际年份减去1900 */
int tm_wday; /* 星期 – 取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */
int tm_yday; /* 从每年的1月1日开始的天数 – 取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */
int tm_isdst; /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/
};
ANSI C标准称使用tm结构的这种时间表示为分解时间(broken-down time)。
而日历时间(Calendar Time)是通过time_t数据类型来表示的,用time_t表示的时间(日历时间)是从一个时间点(例如:1970年1月1日0时0分0秒)到此时的秒数。在time.h中,我们也可以看到time_t是一个长整型数:
大家可能会产生疑问:既然time_t实际上是长整型,到未来的某一天,从一个时间点(一般是1970年1月1日0时0分0秒)到那时的秒数(即日 历时间)超出了长整形所能表示的数的范围怎么办?对time_t数据类型的值来说,它所表示的时间不能晚于2038年1月18日19时14分07秒。为了 能够表示更久远的时间,一些编译器厂商引入了64位甚至更长的整形数来保存日历时间。比如微软在Visual C++中采用了__time64_t数据类型来保存日历时间,并通过_time64()函数来获得日历时间(而不是通过使用32位字的time()函 数),这样就可以通过该数据类型保存3001年1月1日0时0分0秒(不包括该时间点)之前的时间。
在time.h头文件中,我们还可以看到一些函数,它们都是以time_t为参数类型或返回值类型的函数:
double difftime(time_t time1, time_t time0);
time_t mktime(struct tm * timeptr);
time_t time(time_t * timer);
char * asctime(const struct tm * timeptr);
char * ctime(const time_t *timer);
此外,time.h还提供了两种不同的函数将日历时间(一个用time_t表示的整数)转换为我们平时看到的把年月日时分秒分开显示的时间格式tm:
struct tm * gmtime(const time_t *timer);
struct tm * localtime(const time_t * timer);
通过查阅MSDN,我们可以知道Microsoft C/C++ 7.0中时间点的值(time_t对象的值)是从1899年12月31日0时0分0秒到该时间点所经过的秒数,而其它各种版本的Microsoft C/C++和所有不同版本的Visual C++都是计算的从1970年1月1日0时0分0秒到该时间点所经过的秒数。
算法总结:
(一)判断一个数是不是素数
1. 一个数若可以进行因数分解,那么分解时得到的两个数一定是一个小于等于sqrt(n),一个大于等于sqrt(n),据此,代码并不需要遍历到n-1,遍历到sqrt(n)即可,因为若sqrt(n)左侧找不到约数,那么右侧也一定找不到约数。
2.首先看一个关于质数分布的规律:大于等于5的质数一定和6的倍数相邻。例如5和7,11和13,17和19等等;
证明:令x≥1,将大于等于5的自然数表示如下:······ 6x-1,6x,6x+1,6x+2,6x+3,6x+4,6x+5,6(x+1),6(x+1)+1 ······
可以看到,不在6的倍数两侧,即6x两侧的数为6x+2,6x+3,6x+4,由于2(3x+1),3(2x+1),2(3x+2) ,所以它们一定不是素数,再除去6x本身,显然,素数要出现只可能出现在6x的相邻两侧。这里要注意的一点是,在6的倍数相邻两侧并不是一定就是质数。
根据以上规律,判断质数可以6个为单元快进。
代码如下: if((num%6!=1)||(num%6!=5)) return 0;
int tem=sqrt(num);
for(i=5;i<=tem;i+=6)
if((num%i==0)||(num%(i+2)==0))
return 0; //排除所有
return 1;
(二)用牛顿迭代法求方程的根
牛顿迭代法:先任意设定一个与真实的根接近的Xk作为第一次近似根,由Xk求出f(Xk)。再过(Xk,f(Xk))点做f(x) 的切线,交x轴于Xk+1,它作为第二次近似根。给出误差范围即可。
由函数图像可得f'(Xk)=f(Xk)/(Xk-Xk+1),因此Xk+1=Xk-f(Xk)/f'(Xk);由Xk依次推出Xk+1,Xk+2,.......
例如f(x)=2x^3-4x^2+3x-6=((2x-4)x+3)-6,用后面的表达式可以节省时间。
代码如下:
do{
x0=x;
f=((2*x0-4)*x0+3)-6; /*邱发f(x0)*/
f1=(6*x0-8)*x0+3; /*求导数*/
x=x0-f/f1;
}while(fabs(x-x0)>=1e-5);
(三)排序算法
1.冒泡排序法
基本思路:将待排序的相邻数据元素两两比较,若逆序则交换。当从前向后依次扫描比较之后,最大的数会被放在最后一个位置,这个过程称为一趟排序。
第二趟扫描到倒数第二个数。
从前向后扫描,大数后移;从后向前扫描,小数前移。
N个数据,需N-1趟排序;第一趟排序,需要N-1次两两比较;第j趟排序,需要N-j次两两比较。
代码如下:
for(j=1;j<=N-1;j++)
for(i=0;i<N-j;i++)
if(a[i]>a[j])
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
改进算法:若某趟排序过程中没有元素交换,则说明已经达到有序状态。
2.选择排序法
基本思路:先在a[0]~a[N-1]中找到最小的元素,放在第一个位置;再从a[1]~a[N-1]找到最小的元素,放在第二个位置;共需进行N-1轮比较。
代码如下:for(i=0;i<N-1;i++)
{
k=i;
for(j=i+1;j<N;j++)
if(array[j]<array[k]) k=j; //用k记住所找的数中 最小的下标,避免了不必要的多次交换和比较
if(i!=k)
{
t=array[k];
array[k]=array[i];
array[i]=t;
}
}
(四)将数组元素逆过来,只能借助一个临时存储单元
方法:将前后的两个元素依次对调。
i=0,j=n-1; i=1,j=n-2; -->j=n-i-1
p=N/2-1;
(五)将字符串s转换成相应的双精度浮点数
库函数已经提供了功能,下面为思路;
首先对字符串中出现的'+'和'-'进行处理,其次处理'.'前的部分,即将每一个数字字符转换成相应的数值,并加权累加成相应的整数部分。
i=0;
sign=1;
if((s[i]=='+')||(s[i]=='-')) //sign处理符号
sign=(s[i++]=='+')?1:-1;
for(val=0;s[i]>='0'&&s[i]<='9';i++) //处理整数数字部分
val=10*val+s[i]-'0';
if(s[i]=='.')
i++;
for(power=1;s[i]>='0'&&s[i]<='9';i++) //处理小数数字部分
{
val=10*val+s[i]-'0';
power*=10;
}
number=sign*val/power;
(六)统计文本中的单词个数
对单词个数加1必须同时满足两个条件:第一,当前所检查的这个字符非空格;第二,所检查字符的前一个字符是空格。
代码如下:
prec=' ';num=0;i=0;
while(str[i]!='\0')
{
nowc=str[i];
if(nowc!=' '&&prec==' ') num++; //是新单词,个数加1
prec=nowc; //将已检查的字符保存至prec,继续检查
i++;
}
(七)找鞍点(行上最大,列上最小的元素)
如三行四列,代码如下:
find=0;i=0; //find为0,标志还未找到鞍点
while(i<3&&(find==0))
{
rmax=a[i][0];c=0;
for(j=1;j<4;j++)
if(rmax<a[i][j])
{
rmax=a[i][j]; c=j; //某行最大值所在列号c
}
find=1; k=0; //先假设行上的最大值为列上的最小值
while(k<3&&(find==1)) //内循环查rmax是否为c列上的最小数
{
if(k!=i)
if(a[k][c]<=rmax)
find=0;
k++;
}
}
(八)用计算机洗扑克牌
将54张牌编号为0,1,2,,,,,,,53。
存储54张扑克牌:数组PK,每个数组元素是一位三位数,第一位表示种类,后两位表示牌号。
将用到的库函数:rand()用于产生随机数,srand函数时随机数发生器的初始化函数,它需要提供一个种子,如srand(1)。
不过通常使用系统时间来初始化,即使用time函数来获得系统时间,它的返回值为从00:00:00 GMT ,January,1,1970到现在持续的秒数。
在进行抽取操作时,首先在0~53之间选择一个随机数r=rand()%53,将pk[0]与pk[r]交换;接着在1~53之间产生一个随机数r=rand()%(53-1)+1,将
pk[1]与pk[r]交换。。。即在i~53之间产生一个随机数r=rand()%(53-i)+i,将pk[i]与pk[r]交换,直到所有牌都被交换。
代码如下:
int pk[54]={501,502,
101,102,.................};
srand(time(0)); //用随机数设置随机数序列的
for(i=0;i<53;i++)
{
r=rand()%(54-i)+i; //产生i到53之间的随机数
temp=pk[i];
pk[i]=pk[r];
pk[r]=temp;
printf("%d",pk[i]);
}
(九)计算数组s中数组t出现的最右边位置
代码如下:
for(i=0;s[i]!='\0';i++)
{
for(j=i,k=0;t[k]!='\0'&&s[j]==s[k];j++,k++)
;
if(t[k]=='\0')
return i;
}