强烈推荐C学习书籍:《C程序设计》 谭浩强著(外号:红皮书)
注:本C语言大部分总结来自于本书。
C对很多刚入门的同学来说也算是基础课,所以在这里说一下自己的心得:
入门学习C要有以下几步:
1.熟悉C的各个模块,选择、循环、判断。
2不要觉得自己看过就会,都是人,都会忘记。最重要的是上机。很多东西是你要上机之后才能发现问题的,当然这也是对自己基础检验的一个过程。
3.基础过后,要尝试做一些简单的算法题,算法题一定要自己想,动笔把算法题第一步干嘛,经历第一步会得到什么结果,这个过程至关重要,对你对代码逻辑的形成至关重要。大概20多道题,你就会发现自己C小有所成。
4.C重点难点是指针,这个时候你要发挥刨根问底的精神,其实你了解这个过程之后,感觉很简单。
注意:每个人学习C的目的不同,如果你是靠C吃饭,一定要仔仔细细看清每个知识点。而我的目的是考试,所以我会把我认为干的东西总结下来。
NEXTPART LET’S BEGIN !
1.整形常量
2 实型常量:小数、指数指数:字母e或E表示以10为底的指数。e或E之前必须有数字,且e或E后面必须为整数。不能写成 e666 或者 3e55.5
3 字符常量:普通字符和转义字符
(1)普通字符:单撇号括起来的一个字符,如 ‘a’ ,且单撇号只是界限符,字符常常量只能是一个字符,不包括单撇号。内存中以ASCII代码存储。
(2)常用ASCII代码:‘A’ ~ ‘z’ —> 65 ~ 90 , ‘a’ ~ ’ z’ —> 97 ~ 122
(1)转义字符:\n (换行) 、 \r (回车)、\t (水平制表符,一个tab为8列) 、\o、\oo或\ooo (八进制码对应ASCII码字符,其中o代表一个八进制数字)、\xh[h…] (十六进制码对应ASCII码字符,其中h代表一个十六进制数字)
(2)进制转换:十进制转二进制、十进制转八进制、十进制转十六进制 :(除A取余法:A即你要转换的进制数,即每次将整数部分除以A,余数为该位权上的数 , 而小数部分为 乘A取余法 。)例如:将十进制数796.703125转换为八进制数,整数部分 796除8商99余数4,取4 , 99除8商12余数3,取3 , 12除8商1余数4,取4 , 1除8除不开,取1 , 从下往上读,整数部分为:1434 , 小数部分 0.7031258=5.625 取5 , 0.6258=5 取5 , 小数部分从上往下读,为:55 , 得到结果十进制796.703125转换八进制为1434.55。
二进制与八进制:(巧记421,即3位3位看) 例如:将二进制数110110.111转换为八进制 ,二进制110对应6(4+2) ,所以整数部分是66,小数部分:二进制111对应7(4+2+1),所以 二进制数110110.111转换为八进制为66.7 。而八进制转换二进制即为二进制转换八进制的逆推。
二进制与十六进制:(巧计8421,即4位4位看,十六进制中:10对应A,11对应B,12对应C,13对应D,14对应E,15对应F)将二进制11101001.1001转换为十六进制 从小数点开始向左取四位读数,1001对应9(8+1),1110对应14(8+4+2),整数部分为149,注意了14在16进制中写为E,所以是E9 ,从小数点开始向右取四位读数,1011对应11(8+2+1),注意了11在16进制中写为B,得到结果:将二进制11101001.1011转换为十六进制为E9.B 。
注意八进制和十六进制不能直接转换,应该先转换为二进制,之后转换到十六进制或八进制4 字符串常量 : 双撇号将若干字符括起来,字符串常量是双撇号中全部字符(不包括双撇号本身)
注意:
字符串常量和前面字符常量的区别:单撇号只能包含一个字符,而双撇号可以包含一个字符串!!!!5 符号常量:
(1)#define 指令,指定一个符号代表一个常量。
(2)符号常量不占内存,只是临时符号,预编译后这个符号就不存在了,故不能对符号常量赋以新值。
(3)优点:1)含义清楚 2)在需要改变程序中多处用到同一个常量时,能做到 “ 一改全改 ”。
注意:
符号常量和变量区别:符号常量不占内存,只是一个临时符号,预编译就不存在了,故不能赋新值。而变量有类型,占存储单元,可赋新值。
1.变量
(1)必须先定义,后使用。定义时指定变量名字和类型。
(2)变量名实际上是以一个名字代表的一个存储地址,编译系统通过变量名找到相应的内存地址,从该存储单元中读取数据。2.常变量 (例如 : const int a = 3)
注意:
(1)常变量与常量的异同:常变量具有变量的基本属性,有类型,占存储单元,只是不允许改变其值。(可以将常变量理解为有名字的不变量!)
(2)符号常量和常变量的区别:定义符号常量用#define指令,它是预编译指令,用一个符号代表支付穿,仅仅是进行字符替换(与优先级无关!!!),预编译后符号常量就不存在了,且不分配存储单元。3.标识符
C语言规定:标识符只能由 数字、字母、下划线 组成,且第一个字符必须为字母或下划线,且C语言区别大小写。
1.基本整形(int 型)
(1)存储方式:正数a:a的二进制形式存储。负数b:b的绝对值写成二进制形式,之后按位取反,再加1。
(2)短整型(short int):占2个字节,存储方式与int相同。
(3)长整型(long int):占4个字节,在一个整数末尾加大写字母L或者小写字母l,表示它是长整型。
(4)有符号整形和无符号整形( signed \ unsigned ):1)既未指定signed也未指定unsigned,默认为“有符号整型”,如signed int a 和 int a 等价。
2)有符号整型数据存储单元中最高位代表符号(0 为正,1为负),如果指定unsigned(为无符号)型,存储单元中全部二进位都用做存放数值本身,无符号位。
3)unsigned 类型取值范围 : 占2字节:0~65535( 2 的十六次方 - 1),占四字节:2的32次方 - 1 ,即 无符号整形不能取负。
4)只有整型(包括字符型)数据可以加signed或者unsigned修饰符,实型数据不能加。
5)对于无符号整形数据 “ %u”格式输出,%u表示用无符号十进制数。
(1)字符与字符代码
1)字符是按照其ASCII码形式存储的。
2)各种字符集(包括ASCII字符集)的基本集包括了127个字符。
3)127个字符最多用7个二进位就可以表示,所以指定一个字节存储一个字符,字节中的第一位置为0。(所有系统不例外)
4)区分字符 ‘1’ 和 整数 1 的区别:字符 ‘1’ 代表一个形状为‘1’ 的符号,需要的时候原样输出,在内存中以ASCII码形式存储,占一个字节。而整数1是以整数存储方式(二进制补码方式)存储,占2个或4个字节。
(2)字符变量
- “ %c ” 格式输出字符 , scanf("%c",&a); 这个时候因为输入的字符,回车也会当作一个字符存储在缓冲区中。
2)有符号整形溢出循环: ( x + 128 )% 256 - 128
(1) float : 占4个字节,6位有效数字,在浮点数后面家F或f,就表示为float型常量。且双精度型变量值要用格式符%f表示。
(2) double : 占8个字节,15位有效数字,在浮点数后面家L或l,就表示为long double型。 且双精度型变量值要用格式符%lf表示。
(3) C编译系统把浮点型常量都按双精度处理,分配8个字节。
(1)两个实数相除结果是双精度 ,两个整数相除结果是整数。
(2)%运算符要求参加运算的运算对象(即操作数)为整数,结果也是整数。
(3)自增运算符(++)和自减运算符(- -)只能用于变量,而不能用于常量或表达式。在循环语句中,循环变量自动加1,也用于指针变量,使指针指向下一地址。且自增自减后原值跟着改变。注意自增自减的使用:++a 是先a先自增1,再进行其他操作,- -a是先自减1,再进行其他操作。a++先进行当前操作,之后自增1 , a- -先进行当前操作,之后自减1 。(自减中间无空格,为了看清楚才加的空格。)
(4)算术运算符结合方向 “自左向右” ,赋值运算符结合方向“自右向左”。
(5)运算时,先转变量都换成其中占位数最高的类型,之后进行运算(有double,先都转换成double型,之后运算)
(6)字符型与整形运算,先将字符转换成ASCII码之后与整形进行运算。
(1) 强制类型转换运算符:(类型名)(表达式) 例如 (int)(x + y) : 将 x+y 的值转换成int型
(2)强制类型转换时,得到所需中间数据,原来变量的数据的类型没有发生变化。
(3) 强制类型转换符优先级高于%运算。
(4) 优先级:单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 条件运算符 > 赋值运算符。
(1) 一般形式:变量 赋值运算符 表达式
(2) 赋值运算符左侧应该是一个可修改的左值(出现在赋值运算符左侧,值可修改)变量可作为左值,表达式、常量均不能作为左值(值不能修改),所以凡是左值都可以作为右值。
(3)浮点型数据(单双精度)赋给整型变量,先对浮点数取整,即先舍弃小数部分,之后赋予整型变量。
(4)float型数据赋给double变量时,数值不变,内存中以8个字节存储,有效位扩展到15位。
(5) 截断:将一个占字节多的整形数据赋给一个占字节少的整型变量或者字符变量,只将其低字节原封不动送到被赋值变量。
(6)实型数据之间以及整型数据与实型数据之间的赋值,是先转换后赋值。
(7) 赋值表达式的作用:1. 赋值 2.判断
(1) #include< … >称为标准方式:是为了使用系统库函数,因而要包含系统提供的相应头文件。
(2) #include" … " ,用户使用的是自己编写的文件,用双撇号形式。双撇号中可写出文件路径(如#include"C:\temp\file1.h")
(3)使用双引号:系统现在当前目录下查找,找不到再按表专访时查找。
(1) d 格式符:用来输出一个有符号的十进制数。
格式控制:%5d,表示域宽(所占列数,靠右)例如:printf("%5d",a); 输出 12 , 12前面有3个空格。
(2) c格式符:用来输出一个字符,也可以指定域宽,同上。
(3) s格式符: 输出一个字符串。
(4) f格式符,输出实数(单、双精度)小数输出指定数据宽度和小数位数,用%m.nf,%7.2f指定 输出的数据占7列,其中包括2位小数,注意小数点占一位。
例如: printf("%20.15f\n",a/3); 运行结果: 0.333333333333333,即:3个空格,1个0,一个点,15个3。
%-m.nf:当数据不超过m时,数据向左靠,右端补空格。
注意:一个双精度只能保证15位有效数字的精确度!(5) e格式符:%e指定以指数形式输出实数
(6) o格式符:以八进制整数形式输出,将内存单元中的各位的值(0 或 1) 按八进制形式输出,因此输出的数值不带符号。
(7) x格式符:以十六进制数形式输出整数
(8) u格式符:用来输出无符号型数据,以十进制形式输出。
(1)格式控制后面跟的是地址,所以如果是指针、数组名则不用家 & 取地址符号,如果是变量则要加 & 取址符,代表取地址。
(2)输入格式控制后,输入时候一定要按格式控制输入,不然会出错。例:scanf(“a=%d,b=%d”,&a,&b): 正确输入:a=1,b=2 错误输入:1,2
(3)“%c” 格式声明输入字符的时候,空格字符和“转义字符”中的字符都作为有效字符(包括空格和回车)
输入数值时,两个数值之间要插入空格(或者其他分隔符),即当输入数值时候,系统遇到非数值符号就认为第一个数据输入结束。
例如:scanf("%d%c%f",&a,&b,&c): 输入1234a123o.26 系统会将1234送给变量a,字符a送给b,123送给c,后面字符未读入。
输入字符时,两个字符之间不需要插入空格或其他分隔符
(1) putchar :输出一个字符,而不能输出整数。
putchar(’\n’); 输出一个换行符。
putchar(’\101’); 输出字符A
putchar(’’); 输出单撇号字符。(2) getchar:输入一个字符。
getchar函数只能接收一个字符,注意getchar会接受回车,将回车作为一个字符。
getchar函数得到的字符可以赋给一个字符变量或整型变量
也可以作为表达式的一部分。putchar(getchar()); 即将接收到的字符直接输出。
(1) 两种选择结构: if(双分支选择)、switch(多分支选择)
(2) 复合语句应该用花括号括起来。
(3) 判断的结果是一个逻辑值。
(4) 关系运算符将两个数值或数值表达式连接起来的式子,称关系表达式。
(5) 关系表达式的值是一个逻辑值,即“真” 或 “假” 。
(6) && 逻辑与 (二者均为真,返回 真) , || 逻辑或 (二者之一为真,返回 真) , ! 取非(为假 ,返回 真)
(7) 表示逻辑运算结果时,以数值1代表“真” , 以 0 代表“假” 。
(8)a && b && c 只有a为真(非0),才需要判断b的值,只有当a和b都为真的情况下才需要判别c的值。如果a为假就不必判断b和c(此时整个表达式已经确定为假),a为真,b为假,就不用判别c。
(9) a || b || c 。只要a为真(非0),就不必判断b和c。 只有a为假 ,才判别b。a 和 b都为假才判别c。
用bool来定义,只有true 和 false 两个值,true代表1 , false代表0。
(1) 三目运算符: 表达式1 ?表达式2 :表达式3 ,表达式1成立返回表达式2的值,表达式1不存在返回表达式3 。
(2) 条件运算符:>、>=、<、<= 以上四者优先级相同,且优先级大于以下两种:== 、!= (优先级相同)
(1)应当注意:if 与 else 配对关系中,else总是与最近的未配对的 if 配对。
(2)if( ){ … } else{ … } 算一个语句。
(1)在每个case后面的语句中,最后都有一个break语句。它的作用是使流程转到switch语句的末尾。当不设置break语句时,执行下一条case语句,如果一直不设置break语句,则多个case标号可以共用一组执行语句。
(2) switch后面括号内的“表达式”,其值的类型应该为整数类型(包括字符型)
(3) switch下面的花括号内是一个复合语句,语句体内包含若干条case开头的语句和最多一个default开头的行。
(4) 可以没有default标号,此时没有与switch表达式相匹配的case常量,流程转去执行default后面语句。
(5) 各个case标号出现次序不影响执行结果,且每个case常量必须互不相同。
(6) case语句中虽然包含一个以上执行语句,但可以不用花括号括起来,加上也可以。
(7)体验switch的妙用:
输入某天的年月日,计算改天为当年的第几天,利用下面的结构体。
typedef struct Data{
int year ;
int month ;
int day ;
}Data;
int main() {
int num = 0;
Data data = { 2019,10,1};
num = Calculate(data);
if((data.year % 4 == 0 && data.year % 100 != 0 ) || data.year % 400 == 0)
{
if( data.month > 2) printf(“data is %d”,num+1+data.day);
else printf(“data is %d”,num+data.day);
}
else printf(“data is %d”,num+data.day);
}
int Calculate(Data data){
int num = 0;
switch(data.month - 1)
{
case 11: num += 30;
case 10: num += 31;
case 9: num += 30;
case 8: num += 31;
case 7: num += 31;
case 6: num += 30;
case 5: num += 31;
case 4: num += 30;
case 3: num += 31;
case 2: num += 28;
case 1: num += 31;
}
return num;
}
注:本算法中,由于没有break语句,所以无论匹配到哪个case语句,都会向下执行,即讲下面的每个月的日子都会加在一块。
while和do while相同点与不同点:
相同点:表达式为真就执行循环语句。
不同点:while是先判断循环条件是否成立,do while是先执行一遍循环体,再进行判断循环条件。通常不知道循环次数的情况下比较方便(具体情况具体分析)
(1) for循环不仅可用于循环次数已经确定的情况,也可用于不确定的情况
(2) for(表达式1 ;表达式2;表达式3 ) 语句 等价于 for(循环变量赋初值;循环条件;循环变量增值) 语句
(3) for循环转换为while循环:
for(表达式1;表达式2;表达式3) 语句
表达式1;
while (表达式2)
{
语句;
表达式3;
}
(4)表达式2可省略,即for( i = 1 ; ; i++) sum += i; 此时因为没有循环条件,系统认为表达式2始终为真,无休止执行下去。
(5)表达式1和表达式3也可省略,不过要在循环之前给循环变量赋初值,在循环体中设法满足循环条件结束的语句。如果不给循环变量赋值,出错。
(6) 表达式1和表达式3可以是一个简单的表达式,也可以是逗号表达式,即包含一个以上的简单表达式,例如for(j = 0 , i = 100 ; i <= 100 ; j++ , i–) k = i + j;
(7)循环体中逗号表达式按照自左向右顺序求解,整个逗号表达式的值为最右边的表达式的值。
(8)表达式2可以是关系表达式(如 i < 100)或逻辑表达式(如a < b && x < y),但也可以是数值表达式或字符表达式,只要其值为非零,就执行循环体。
(1) 一般情况下三种循环可相互替代。
(2) 凡是while循环能完成的,用for都能实现。
(3) while循环、do…while循环和for循环,都可以用break语句跳出循环,用continue结束本次循环。
(1) break语句:结束整个循环,不再判断循环条件是否成立。
break语句只能用于循环语句和switch语句之中,而不能单独使用!
(2) continue语句:只结束本次循环,直接执行下一次循环(即直接执行for循表达式3,之后判断循环条件)。
(3) 双重循环,在内循环体内有一个break语句,执行时会提前终止内循环。
(1)数组:一组有序数据的集合,数组中每一个元素都属于同一个数据类型。
(2)数组下标是从0开始的。
(3)数组是静态的,不能对数组的大小做动态的定义。
(4)被调用函数中定义数组,其长度可以是变量或者非常量表达(每次形参从实参获取数组长度,所以可变长),称之为可变长数组。(指定数组为静态存储方式static,则不能用“可变长数组”。)
(5) 引用数组:1)引用数组元素,只能一个一个引用,不能一次整体调用整个数组全部元素的值。
2)数组下标可以是整型常量或整型表达式。
(1)初始化时,可给一部分元素元素赋值、也可只给数组中一部分元素赋值。
(2)数组初始化都为0: int a[10] = {0};
(3)对于全部数组元素赋初值时,由于数据个数已经确定,可不指定数组长度。
(4)数组长度与提供初值的个数不相同,则方括号中的数组长度不能省略。
(5)定义数值型数组时,指定了数组的长度并对之初始化,凡未被“初始化列表”指定初始化的数组元素,系统自动将他们初始化为0(字符型数组,初始化为’\0’ , 如果是指针型数组,则初始化为NULL,即空指针。)
(1)二维数组可以理解成N个一维数组,因为C语言中二维数组中元素排列的顺序是按行存放的。
(2)一堆方括号内不可以写两个下标。
(3)区分定义数组时的a[3][4]和引用元素时a[3][4]的区别。前者时定义数组的维数和各维的大小,后者中的3、4是数组元素的下标值
(1)
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12 };
(2)int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
(3)int a[3][4] = { {1},{0,6},{0,0,11} };
—对部分元素初值,其余元素值自动为0.
(4)对全部元素赋初值(提供全部初始数据),定义数组时第1维长度可以不指定,但2维不能省略,因为系统要根据第2位维长度算出第1维的长度。
(5)定义部分元素初值而省略第一维长度,应分行赋初值。
(1)C语言中没有字符串类型,字符串是存放在字符型数组中的。
(2)字符型数据以ASCII码形式存放,因此也可以用整型数组来存放字符型数据。
(3)定义字符数组不进行初始化时,数组中各元素的值是不可预料的,如果花括号中提供初值个数大于数组长度,出现语法错误,小于数组长度,其余自动赋值为空字符( ’ \0 ’ )。
(4)初始化时,初值个数和预定数组长度相同,在定义时可以省略数组长度。
(1)C语言中,字符串作为字符数组来处理的。
(2)字符串结束标志:’ \0 ‘,存储字符串时会自动加一个’\0’ 作为结束符。(’\0’ )
(3)字符串数组存放多个字符串时,数组长度大于最长的字符串长度。
(4)char c[] = "I am happy";
注意,此时数组c的长度不是10,而是11.因为字符串常量最后由系统加上了一个’\0’
(5)可以这样记:字符串数组:数组长度 = 字符串长度 + 1;
(6)字符串数组并不要求它的最后一个字符为‘\0’ ,甚至可以不包含‘ \0 ';
(1)逐个字符输入输出,用格式符“%c”输入或输出一个字符。
(2)将整个字符串一次输入和输出,用格式符“%s”。
(3)输出字符中不包括结束符’ \0 ’ , 且只输出到遇到第一个 ’ \0 '结束 ;
(4)scanf可输入一个字符串:scanf("%s",c);
,这里的c是数组名或者指针。
(1)puts——输出字符串函数:puts( 字符数组 ),puts函数输出字符串可包含转义字符,’ \0 '结束。
(2)gets函数——输入字符串函数,作用:输入一个字符串到字符数组,并且得到一个函数值(字符数组起始地址)。
(3)一般利用gets函数的函数的目的是向字符数组输入一个字符串,而不大关心其函数值。
(4)puts和gets函数只能输出或输入一个字符串,不能写成。
(5)strcat——字符串连接函数:字符串2连接到字符串1后面,结果放在字符数组1中。
(6)strcpy和strncpy函数——字符串复制函数:strcpy(字符数组1,字符串2);
strcpy:字符串2复制到1中去,字符数组1必须写成数组名的形式,字符串 2可以是字符数组名,也可以是一个字符串常量。
(7)不能将一个字符串常量或字符数组直接给一个字符数组,赋值语句只能将一个字符给一个字符型变量或字符数组元素。
(8)strncpy:将字符串2中前难免n个字符复制到字符数组1中去。
(9)strcmp函数——字符串比较函数:strcmp(字符串1,字符串2)
字符串1 > 字符串2,返回一个正整数。
字符串1 < 字符串2,返回一个负整数。
字符串1 = 字符串2,返回函数值0。(10)strlen函数——测字符串长度函数:所得为字符串实际长度,不包括’ \0 ';
(11)strlwr函数——转换小写字母
(12)strupr函数——转换为大写的函数
(13)使用字符串处理函数,要在本文件开头添加:#include
(1)函数声明的作用:
1)把有关函数的信息(函数名、函数类型、函数参数的个数与类型)通知编译系统,以便在编译系统对程序进行编译的时候,知道它们市函数而不是变量或者其他对象。
2)对调用函数的正确性进行检查(如类型、函数名、参数个数、参数类型等是否正确)。(2)函数的种类:
1)无参函数:调用无参函数的时,主调函数不向被调用函数传递数据。无参函数一般用来执行指定的一组操作。
2)有参函数:调用函数时,主调函数在调用被调用函数时,通过参数向被调用函数传递数据,一般情况下,执行被调用函数时会得到一个函数值,供主调函数使用,此时有参函数应定义为与返回值相同的类型。
(1)定义函数包括:
1)指定函数的名字,以便以后按名调用。
2)指定函数的类型,即函数返回值的类型。
3)指定函数的参数的名字和类型,以便在调用函数时向它们传递数据。
4)指定函数完成的操作,即功能。(2)函数体包括声明部分和语句部分。
(3)定义函数时要用“类型标识符(即类型名)”指定函数值的类型,即指定函数带回来的值的类型。
(4)声明部分包括对函数中用到的变量进行定义以及对要调用的函数进行声明。
(5)函数中return的作用是指将返回值带回到主调函数,返回值类型与函数类型应是相同的。
(1)函数调用语句:不要求函数待会值,只要求函数完成 一定的操作。
(2)函数表达式:这时要求函数待会一个确定的值参加表达式的运算。
(3)函数参数:函数调用作为另一个函数调用时的实参。
(4)作为函数调用语句才需要有分号。如果作为函数表达式或函数参数,函数调用本身是不必有分号的。printf("%d",max(a,b););
这里的max(a,b)后面多了一个分号。
(1)形参和实参:定义函数时函数名后面括号中的变量名称为“形式参数”或“虚拟参数”,在主调函数中调用一个函数时,函数名后面括号中的桉树成为“实际参数”(简称实参),实参可以是常量、变量或表达式。
(2)数据传递:系统把实参的值传递给被调用函数的形参,该值在函数调用期间有效,可参与函数的运算。在调用函数过程中发生的实参与形参间的数据传递,常称为“虚实结合”。注意,实参与形参的类型应相同或赋值兼容
(3)定义函数时指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。发生函数调用时,函数max的形参被临时分配内存单元。
(4)通过return语句将函数值带回到主调函数。应当注意返回值的类型与函数类型一致。
(5)函数不需要返回值,则不需要return语句。这时函数的类型应该定义为void类型。
(6)调用结束,形参单元被释放。
(7)实参向形参的数据传递是“值传递”,单向传递,只能由实参传给形参,而不能由形参传给实参。
(1)一个函数可以有一个以上的return,执行哪个return语句,哪个起作用。return语句后面括号可加可不加。如
return z 和 return (z)
等价,return后面可以是一个表达式。
(2)函数值类型:在定义函数时指定类型。
(3)定义函数时指定的函数类型一般应该和return语句中的表达式类型一致。
(4)如果函数值的类型和reutrn语句中表达式的值不一致,则以函数类型为准。即函数类型决定返回值类型。
(5)不带返回值的函数,应当定义函数为"void"类型(或称“空类型”),此时函数中不得出现return语句。
(1)函数声明 = 函数原型 + 分号。(函数原型:即函数去掉函数体。)
(2)函数声明中,形参名可以省写,而只写形参的类型。编译系统只关心和检查参数个数和参数类型,而不检查参数名.
这里要注意,在值传递中的时候,实参和形参相同时意义是不一样的,不是同一个东西,指向的不是同一个地址。
(3)简答题:函数“ 定义 ” 和 “ 声明 “ 作用 :1)函数的定义:对函数功能的确定,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。
2)函数的声明:把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查,不包括函数题。
(1)嵌套调用
注意不能嵌套定义,但可以嵌套调用函数。
(2)递归调用
调用一个函数的过程中又出现直接或间接调用该函数本身,称为函数的递归调用。
(1)数组元素作函数参数
1)实参可以是常量、变量、表达式。所以 数组元素可以作为函数实参,但不能用作形参,而数组名可以作实参和形参(传递的是数组的第一个元素的地址、注意数组名是一个地址)。
2)数组元素作函数实参时,实参值传给形参,是” 值传递 “方式,数据传递方向实参到形参,单向传递。(2)数组名作函数参数
1)数组名作为函数的实参时,向形参(数组名或指针变量)传递的是数组首元素的地址。
2)实参、形参数组类型应该一致。
3)由 1)可知,数组名作为函数参数的时候,传递的是地址,指向同一片地址空间,所以形参数组元素值改变则实参数组值也改变。
4)多维数组名作为函数实参和形参,在被调用函数中对形参数组定义时可以指定一维的大小,也可以省略一维的大小说明,但二维以及其他高维的大小说明不可省略。
(1)局部变量
1)局部变量两种定义方式:函数开头定义、复合语句内定义。(均为函数内部定义)
2)函数内部定义的变量只在本函数范围内有效,即只能在本函数内调用,函数外无法调用。
3)复合语句内定义的变量只在本复合语句内有效,即只有在本复合语句内才能引用它们,复合语句以外不能使用这些变量
4)形式参数也是局部变量。(2)全局变量
1)函数外定义的变量成为全局变量(也称全程变量)。
2)全局变量可以为本文件中其他函数所共用,作用范围是从定义变量的位置到本文件的结束。局部变量和全局变量同名,会怎样?
在局部变量的作用范围内,局部变量有效,全局变量被“屏蔽”,即全局变量不起作用。
(1)变量作用域 ( 空间 ) 角度观察:全局变量和局部变量。
(2)存在的时间(生存期)角度观察:静态存储方式和动态存储方式。1)静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式
2)全局变量全部存放在静态存储区中,程序开始执行时给全局变量分配存储区,程序执行完毕释放,占固定存储单元。
3)动态存储方式是指在程序运行期间根据需要进行动态的分配空间的方式。
4)函数形式参数(调用函数时分配存储空间)、函数中未用static声明的变量,即自动变量、函数调用时现场保护和返回地址等,以上数据函数调用开始分配空间,函数调用结束释放这些空间,这种分配和释放是动态的。(3)每一个变量和函数都有两个属性:数据类型和数据的存储类别,其中存储类别分为:自动变量(auto)、静态局部变量(static局部变量)、寄存器变量(register变量)、外部变量(extern变量)
1)自动变量:函数中的局部变量,关键字“auto”可以省略,不写auto则隐含指定为“自动存储类别”,属于动态存储方式。
2)静态局部变量(static局部变量):函数调用结束,所占存储单元不释放,下一次调用时的值为上一次调用结束的值,即只在赋初值一次即可。
3)寄存器变量:局部变量的值存放在CPU中的寄存器中,属于局部变量。
4)在一个文件内扩展外部变量的作用域: 用extern对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置,extern声明外部变量时,类型名可以省写(因为不是定义变量,所以可以不指定类型。
5)两个文件引用同一个外部变量:一个文件中定义外部变量Num,在另一文件中用extern对Num做“外部变量声明”。
6)编译系统如何区别处理extern:现在本文件中找外部变量的定义,找到则在本文件中扩展,找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件,再找不到则报错。(4)简答题:static声明一个变量的作用是:
1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。
2)对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。(5)注意:auto , register 和 static声明变量时,实定义变量的基础上加这些关键字,而不能单独使用。
(1)按作用域角度分:局部变量和全局变量
1)局部变量:
自动变量,即动态局部变量(离开函数,值就消失)。
静态局部变量(离开函数,值就消失)。
寄存器变量(离开函数,值就消失)。
(形式参数可以定义为自动变量或寄存器变量)2)全局变量:
静态外部变量(static声明,只限本文件使用)
外部变量(即非静态的外部变量,允许其他文件引用)(2)按生存期分:动态存储和静态存储
1)动态存储:
自动变量(本函数内有效)
寄存器变量(本函数内有效)
形式参数(本函数内有效)2)静态存储
静态局部变量(本函数内有效)
静态外部变量(本函数内有效)
外部变量(extern声明后,其他文件可引用)(3)按变量值存放的位置分:内存中静态存储区和内存中动态存储区、CPU中的寄存器
内存中的静态存储区:
静态局部变量
静态外部变量(函数外部静态变量)
外部变量(可为其他文件引用)
内存中动态存储区:自动变量和形式参数
CPU中的寄存器:寄存器变量(4)区分变量的声明和定义:
建立在存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。
(5)区分内部函数和外部函数
根据函数能否被其他源文件调用,将函数分为内部函数和外部函数。
(1)指针代表的就是地址!!!
(2)变量值存取的两种方式:直接访问(直接通过变量名访问变量值)、间接访问(先将变量A的地址存在另一变量B中,然后通过B变量来找到变量A的地址,从而访问A变量)。
(3)通过地址找到所需单元,也称地址指向该单元。即指向就是通过地址来体现的。
(a)表示的是直接访问,变量名和变量的地址一一对应。
(b)表示的是间接访问,先找到 存放地址的变量i_pointer,从中获取变量 i 的地址(2000),从而找到变量i的存储单元,然后对它进行存取访问。(4)一个变量的地址称为该变量的“指针”。
(5)专门用来存放另一变量的地址(即地址)的变量,称为“指针变量”。即指针变量的值是地址(即指针)。上述图片中,*i_pointer就是一个指针变量,指针变量名为i_pointer,存放的值是变量 i 的地址2000。
(6)所以注意指针变量和指针的区别:指针变量:存放地址的变量。
指针:是一个地址。
int* p ;p = 2000;
这段代码是错误的,p是指针型变量,而2000是int型常量,所以不能相互赋值。
(1)简答题:本段代码中:第四行的pointer_1和pointer_2和倒数第三行printf函数中的pointer_1和pointer_2有什么区别?1)第四行中:表示定义了两个指针变量,这里的‘ * ’ 只是声明定义的是指针变量。
2)倒数第三行:表示指向两个指针变量,这里的‘ * ’ 是指向的意思。
3)!!!读者要注意,代码第5,6行中,写成*pointer_1 = &a ; *pointer_2 = &b;是不对的。
如果加了‘ * ’号就代表了指向,即变量a,把变量a的地址赋给变量a显然是不合理的!而不加 ‘ * ’号的时候pointer_1,pointer_2代表的是指针变量,把地址赋给指针变量,这样才合理!!*
4)同理,不要把一个整数赋给指针变量,因为系统无法分辨这个整数是地址还是整型变量。(2)在定义指针变量的时候,要指定基类型 。
1)由于不同类型的数据在内存中所占字节数和存放方式是不同的,所以必须知道该数据的类型,才能按存储单元的长度以及数据的存储形式正确地取出该数据。
2)一个指针变量只能指向同一个类型的变量,不能忽而指向一个整型变量,忽而指向一个实型变量。
*!!注意,在考试时候,如果问图片倒数第三行*pointer_1,pointer_2是什么意思?应该完整的说:pointer_1,pointer_2是指向整型数据的指针变量。(3)指向整型数据的指针类型表示为” int * “,读作 ”指向int的指针“或简称”int指针“。
(1)& 取地址运算符。&a是变量a的地址。
(2)* 指针运算符(或称“间接访问” 运算符 ),*p代表指针变量p指向的对象。
(3)
由结果可知,a和b的值并没有交换,而p1和p2的值改变了。需要注意的是,判断语句中,交换的是p1,p2的值,而作为指针变量,p1,p2未交换前分别存放的是a,b的地址。判断语句成立后,只是p1,p2所存的地址做了交换,而原本a,b的值并没有变化。
(4)
1)本段程序中,重点注意形参是两个指针,该如何交换?
要注意的是和(3)例不一样的是,本例采取的是交换a,b的值,而p1,p2的值是不变的。恰恰和(3)例相反。
2)另外注意,temp是int型整型变量,为什么可以将p1直接赋给temp变量?
p1是指针变量,而号的意思表示指向,所以*p1表示指向的变量即a,因为a是一个整型变量,所以可以将其值赋值给temp;
(1)数组元素的指针即数组元素的地址。
(2)数组名代表数组中首元素,即序号为0的地址。int *p = &a[0];
等价于p = a;
(3)当指针指向数组元素的时候,可以对指针进行加减运算,也可自增、自减 。
(4)
(5)注意,以下代码是错误的!!!!!!
int a[10] ; for(a ; a < (a + 10) ; a++) printf("%d",*a);
因为a是数组第一个元素的地址,是一个指针型常量,所以既然是一个常量,当然不能自增自减啦。(注意自增自减的使用,在1.6运算符和表达式已详细描述!)(6)简答题:当用数组名作参数时,如果形参数组中各元素的值发生变化,实参数组元素的值随之变化,这是为什么?
前已介绍,实参数组名待变该数组首元素的地址,而形参是用来接受从实参传递过来的数组首元素的地址的,因此形参是一个指针变量(只有指针变量才能存放地址)
fun( int arr[] , int n);
等价于fun( int * arr , int n)
1)形参和实参都用数组名
2)实参用数组名,形参用指针变量。
3)实参形参都用指针变量
4)实参为指针变量,形参为数组名
(1)学习二维数组指针,一定要将二维数组看成多个一维数组组成。
(2)学习二维数组的指针,可以这么理解:将&当作解密,*当作加密。例如: a[1] (相当于 * (a + 1))表示1行0列地址,&a[1]表示1行的首地址。
(3)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20200204155019336.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2NjU3Nzg4,size_16,color_FFFFFF,t_70
(1)指向由m个元素组成的一维数组的指针变量:
int (*p)[4] 表示定义p为一个指针变量,它指向包含4个整形元素的一维数组。注意 p 两侧的括号不可缺少,如果写成p[4],由于方括号[]运算级别高,因此p先与[4]结合,p[4]是定义数组的形式,然后再与前面的 * 结合,*p[4]就是指针数组。
(2)用指向数组的指针作函数参数:
一维数组名可以作为函数参数,多维数组名也可作函数参数。用指针变量做形参,以接受实参数组名传递来的地址。可以有两种方法:
1)用指向变量的指针变量;
2)用指向一维数组的指针变量;
(1)存储空间的起始地址(又称入口地址)称为这个函数的指针。
(2)int (*p)(int , int ); 的含义定义p是一个指向函数的指针变量,它可以指向函数的类型为整型且有两个整型参数的函数。p的类型用int(*)(int , int )表示。
(3)定义和使用指向函数的指针变量
1)定义: 类型名 (*指针变量名 )(函数参数列表)
2)调用:1 如果要用指针调用函数,必须先使指针变量指向该函数 ,如 p = max ;(只给出函数名不必给出参数)
2 用函数指针变量调用函数时,只须将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括号中根据需要写上实参。如: c = (*p)(a,b);
(1)一个数组,若其元素均为指针类型数据,称为指针数组。
(2)定义一维数组:类型名 * 数组名[数组长度];
(3)
(4)指向指针数据的指针:如图所示,name是一个指针数组,它的每一个元素是一个指针型的变量,其值为地址。name既然是一个数组,它的每一元素都应有相应的地址。数组名name代表该指针数组首元素的地址。name+i 是name[i] 的地址。name + i 就是指向指针型数据的指针。
(1)栈:非静态的局部变量(包括形参)是分配在内存中的动态存储区的,这个存储区是一个称为栈的区域。
(2)堆:C语言允许建立内存动态分配区域的,一存放一些临时用的数据这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要时随时开辟,不需要随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆区。
(3)malloc函数:void * malloc(unsigned int size);作用:在内存的动态存储区中分配一个长度为size的连续空间。
(4)calloc函数:void * calloc(unsigned n , unsigned size )
作用:在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。分配不成功,返回NULL.
(5)free函数:void free(void *p)
作用:释放指针变量p所指向的动态空间,无返回值。
(6)realloc函数:void * realloc( void *p , unsigned int size) ;
作用:如果已经通过malloc函数或calloc函数获得了动态控件,想改变其大小,可以用realloc函数重新分配。