C语言基础学习笔记.docx
- 文档编号:10853431
- 上传时间:2023-02-23
- 格式:DOCX
- 页数:49
- 大小:66.21KB
C语言基础学习笔记.docx
《C语言基础学习笔记.docx》由会员分享,可在线阅读,更多相关《C语言基础学习笔记.docx(49页珍藏版)》请在冰豆网上搜索。
C语言基础学习笔记
C语言基础学习笔记
(第一版修改)
丁炳亮
1数据类型和表达式
1.1计算机数据存储方式
理解与测试:
什么是补码?
我们得先知道模的概念。
模“模”是指一个计量系统的计数围。
如时钟等。
计算机也可以看成一个计量机器,它也有一个计量围,即都存在一个“模”。
例如:
时钟的计量围是0〜11,模=12。
表示n位的计算机计量围是0〜2A(n)-1,模=2A(n)。
“模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。
任何有模的计量器,均可化减法为加法运算。
例如:
假设当前时针指向10点,而准确时间是6点,调整时间可有以下两种拨法:
一种是倒拨4小时,即:
10-4=6;另一种是顺拨8小时:
10+8=12+6=6在以12模的系统中,加8和减4效果是一样的,因此凡是减4运算,都可以用加8来代替。
对“模”而言,8和4互为补数。
实际上以12模的系统中,11和1,10和2,9和3,7和5,6和6都有这个特性。
共同的特点是两者相加等于模。
二进制中整数的补码求法是:
正数的补码为原码,负数的补码是符号位不变其他位全部取反再整个数加1。
我们可以通过下面的代码看下负整数在计算机部的表示。
voidf(intn)
{
unsignedinti;
for(i=1,i<<=15;i;i>>=1)
{
if(i&n)
printf("1");
else
printf("0");
}
printf("\n");
}
main()
{
inta=-0xff;
f(a);
getch();
}
输出的结果是00001
1.2变量与常量
理解与测试:
1)类型声明
在计算机部数据是以字节为单位存储的,但是我们需要的数据类型有很多种,每种数据类型所占字节和存储方式都不一样。
类型声明的作用就是告诉计算机以哪种“格式”去读写该数据数据。
类型说明符变量名1,变量名2,变量名n;
类型说明符有基本类型(字符型、整数型、单/双精度型)、高级类型(结构体型、共用体型、枚举类型)、指针型等,其中指针类型包括指向基本类型的指针类型、指向高级类型的指针型和指向函数的指针类型(指向函数指针声明格式在后面章节)。
2)符号常量的定义
#define标识符常量;
使用该种方法定义符号常量一个是可以使代码便于阅读理解,另一个是方便代码部多个相同常量的统一修改。
总结与注意
在写计算式的时候要考虑变量是否会越界。
一般来说计算式子时是先强制转换成式子中最大存储空间的数据类型(不包括被赋值的变量),还要注意不同的类型数据在不同的编译器中所占的存有可能是不一样的,例如有些编译器整型是占2个字节有些是占4个字节。
同时还要考虑到符号的优先级和结合顺序,如果按符号的优先级和结合顺序运算过程中有越界的那么整个计算结果可能和预想的不一样了,例如inti=100;i=500*i/i;最后i=-155。
1.3输出输入
理解与测试:
1)格式化输入输出函数可以按设定的格式和设定的数据类型接收和输出多个变量。
控制
格式和数据类型的是数据控制符。
2)字符和字符串可以用专门的输出输入函数。
主要有getch();putch();getchar();putchar();
gets();puts();其中getch()和putch()是conio.h中声明,getch()不需要等待回车键就返回字符并立即执行下面一语句符,getch()不在屏幕显示输入的字符。
getchar();putchar();在stdio.h中声明。
getchar()读取键盘上的一个字符,立即返回并显示在屏幕上,需要等待回车键才能执行下
一条语句。
例如下面的代码:
intmain(void)
{
charch,i;
for(i=0;i<10;i++)
{
ch=getchar();
putchar(ch);
}
getch();
}
输入:
ILOVEYOU
输出:
ILOVEYOU
总结与注意
格式输入函数中的参数是变量存放的地址,所以变量前面要”&”符号。
如果是数组则可
以直接用数组名,因为数组名就是指向该数组首地址的指针常量。
1.4运算符号
理解与测试:
1)表达式中符号的运算顺序是有先后的,根据符号的优先级和结合性(左结合/右结合)
来判定。
如果是几个符号连在一起C部是怎么去读的呢?
有一个很简单的规则:
每一个符号应该包含尽可能多的字符。
也就是说,编译器读取符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么在读入下一个字符,直到与下个字符组合成的符号没有意义为止。
例如:
a---b;这个表达式等效于(a--)-b;
2)复合赋值符及表达式在赋值符“=”之前加上其他双目运算符号可以构成复合赋值符。
如:
+=、-=、*=、/=、%=。
构成复合赋值表达式的一般格式为
变量双目运算符=表达式
等价于
变量=变量运算符表达式
可以这么去理解,等号左边的相当于变量自增、自减、自除、自取余一个数,这个数由
右边的表达式计算得到。
如:
inta=7,b=3,c=3;a+=b+=c+=5运算后是a=18,b=11,c=&
3)C中唯一一个三目运算符是条件运算符,由“?
”和“:
”构成。
两个符号必须按下面的格式一起使用:
表达式1?
表达式2:
表达式3
三个表达式可以是任意的合法表达式,条件运算符的运算规则如下:
如果表达式1的值为真,那么整个表达式的值为表达式2的值,否则为表达式3的值。
4)sizeof()也是一个运算符号,它可以对一个数据类型或一个变量取类型长度。
但是当一个数组名作为一个实参传递给函数时,在函数部是不能用sizeof来获取该数组的长度的。
因为数组名是一个指向数组首地址的指针,使用sizeof只能取得实参指针类型长度。
5)比较少用到的符号是“:
”和“,”。
冒号运算符在C中有三种用途,第一种是我们上面讲到的与“?
”构成三目运算。
第二种是在switch中放在常量表达式后面。
第三种用法是在结构体中定义位域时用到。
struct位域结构名
{位域列表};
其中位域列表的形式为:
类型说明符位域名:
位域长度
例如:
structbs
{
inta:
8;
intb:
2;
intc:
6;
};逗号运算符的功能是把两个表达式连在一起组成一个新的表达式,称为逗号表达式。
其一般格式为
表达式1,表达式2,表达式3,...,表达式n;其求值过程是自左向右求两个表达式的值,并以最后一个表达式的值作为整个逗号表达
式的值。
例如for(a=0,b=0,c=0;b<10,a<20;b++,a++)++c运算后c=20,如果把中间的两个语句前后调换for(a=0,b=0,c=0;b<10,a<20;b++,a++)++c运算后c=10。
6)不同的数据类型的量混合运算的时候,系统会临时把算式中长度小的数据类型自动转换成长度大的数据类型。
在某些时候我们需要临时改变一个数据的类型或者说是读取“格式”,我们可以使用强制类型转换符,”(type)”使用的格式一般如下:
(类型说明符)表达式要得到一个数据类型的类型说明符很容易。
只要知道如果声明一个给定类型的变量,那么就可以得到该数据类型的转换符就是把声明中变量名和声明的分号去掉。
例如:
(int)、
(float)、(char*)、(int(*)())等等。
总结与注意
1)“==”和“=”这两个符号也容易在判断语句出错,严重的还会出现死循环。
2)自增“++”和自减“--”可以作为前缀也可以作为后缀。
作为变量前缀时,在表达式中每次取用变量值运算前先自增/自减,作为变量后缀时,在表达式中每次取用变量值运算后再自增/自减。
例如:
main()
{
inti=2,j=1,n;printf("%d\n",i++);n=i++>j?
i++:
j;printf("%d\n",n);getch();
}
输出结果:
2
4
3)在写式子时要注意运算符号对变量的副作用,单目运算有“++”和“--”,双目运算
有“=”。
4)位运算符号与逻辑符号容易混淆的有“&”和“&&”、“|”和“||”、“~”和“!
”。
前面两个可以这么记,位运算是单个的所以符号也是单个的。
5)在使用位运算符号时要主要如果是有符号数则是对他们的补码执行位运算。
对于有符
号的整数型使用左移“<<”右补0,使用右移“>>”左补1。
如下代码:
voidf(intn)
{
unsignedinti;
for(i=1,i<<=15;i;i>>=1)
{
if(i&n)
printf("1");
else
printf("0");
}
printf("\n");
}
main()
{
inta=-0xff;
f(a);
a>>=1;
f(a);
getch();
}
输出的结果是:
6)运算符优先级可以简单归纳如下几点:
1单目运算符比任何一个真正意义上的运算符的优先级高。
2逻辑运算符比关系运算符优先级高。
3移位运算符表算术运算符优先级低,但高于关系运算符。
2程序控制结构
2.1选择结构
理解与测试:
使用if-else多层嵌套时else是与上面同一层的最近if相结合的,在程序设计时有过多的分支最好要选择使用if-else,当然如果可以使用switch语句还是尽量选择switch语句或者使用其他能使代码简洁易懂的方法。
如下面的代码:
main()
{
inta=10,b=6,c=7,d=8,e=9,f=10;
if(a>b)
{
printf("%d\n",a);
if(b>c)
{
printf("%d\n",b);
if(c>d)
{
printf("%d\n",c);
if(d>e)
{
printf("%d\n",d);
if(e>f)
{
printf("%d\n",e);
}
else
printf("%d\n",f);
}
}
}
}
getch();
}
这种代码又长又难看懂,更要命的是经常搞不懂下面的大括号对应上面的哪个大括号。
改进后的代码如下
main()
{
inta=10,b=6,c=7,d=8,e=9,f=10;
while
(1)
{
if(a<=b)break;
printf("%d\n",a);
if(b<=c)break;
printf("%d\n",b);
if(c<=d)break;
printf("%d\n",c);
if(d<=e)break;
printf("%d\n",d);
if(e<=f)
printf("%d\n",e);
else
printf("%d\n",f);
break;
}
getch();
}
第二种方法比第一种方法来看上去更干净,理解上差不了多少。
2)
总结与注意
switch语句中每一个分支结束都要有break语句,如果没有是继续执行下面分支,可能会得到和预料不同的效果。
还要注意,case后面跟的一定是整数型或符号型常量表达式,不能是变量,并且不用有相同值的变量表达式,不然会矛盾的。
2.2循环结构
理解与测试:
1)主要有for、while、do-while这三种结构循环体,其中最后一种是比较少用到的,因为一般情况都可以用前面两种替代。
2)循环体结构中括号的表达式要知道时候退出,有时候还要知道循环体运行多少次。
在循环体部也可以使用continue语句跳过本次循环,进入下一次循环,或者用break语句退出当前层循环。
总结与注意
循环结构中表达式的设计很重要。
在for循环结构我们经常用到从0到x,循环体执行x+1次,可以写成for(i=0;i 有时候要考虑变量i是否会越界,例如: for((int)i=10;i>0;i-=3);这个循环语句的运行结果一般不是我们想要的。 把“==”不注意写成“=”也是容易造成死循环的结果。 3数组 3.1一维数组 理解与测试: 1)一维数组的定义如下: 类型说明符数组名[常量表达式]如果定义时就进行初始化,常量表达式可以省略。 动态存储的数组没有初始化所有元素的值都为随机。 静态存储的数据没有初始化所有的元素自动赋0。 2)一维数组名是一个指针常量,指向数组的首地址。 由于是指针常量所以不能对其赋值,只能当指针引用。 同理一个数组不能对另一个数组整体赋值。 数组引用时下标是从0开始计算的。 3)字符型数组初始化可以直接赋值字符串。 chars[]={“Happy”};或者chars[]=”Happy”;。 总结与注意 数组引用时下标不要越界,字符串数组初始化时,数组元素个数要比字符串多一个,因 为字符串结束有个“\n”符,否则越界就会发生预料不到的结果。 如下: main() { chara[8]="abcdefgh"; printf("%s",a); getch(); } 输出结果: main(){inta[2],i;for(i=0;i<8;i++)scanf("%d",&a[i]); getch(); }上面这个代码当输入字符超过2个时可能出现无法运行的结果。 总结与注意数组名作为函数参数时传递给形参的是数组的指针,而且在子函数中无法用sizeof取得数组的大小,所以有需要时可以用另一个参数传递数组的大小。 数组作为形参时可以写成intf(inta[])或者intf(int*a)或者intf(inta[10]),他们三者都一样的,因为对于指针来说“[]”等效于“”,数组做形参不会传递数组大小,inta[10]等效于inta[]。 数组作为实参时可以写成f(a)或者f(&a[0])或者,因为a[0]的地址就是数组的首地址。 但是要注意f(a[10])将是把a[10]作为实参传递过去,a[10]的值赋值给指针使用是危险的。 3.2多维数组 理解与测试: 多维数组和一维数组相似,只是使用了多个下标。 多维数组其实可以理解为数组的元素又是一个数组。 例如,二维数组可以保存一个矩阵,三维数组可以保存多个矩阵。 4函数 4.1函数的定义和调用 理解与测试: 1)函数定义一般格式如下 函数类型函数名(形参表) { 声明部分; 语句部分; return表达式; }声明部分必须是放在函数的最前面,如果放在语句中则编译不能通过。 2)值参数是指,将实际参数的值传递到函数的形式参数中,这也是参数默认传递方式。 函数调用中发生的数据传递是单向的,即只能把实际参数的只传递给形式参数,在函数调用过程中,形式参数发生改变,而实际参数中的值不会发生变化(形式参数可与实际参数同名)。 引用参数是将实际参数的地址传递给函数的形式参数。 总结与注意 1)形参必须是变量,用于接收实参传递过来的值。 实参可以常量、变量、表达式、函数调用等,无论实参是何种类型的量,在函数调用时都必须有确定的值。 实参与形参,数量和类型要能一一对应。 2)函数如果在定义前使用,或者在其他文件中。 那么必须先声明,再使用。 函数运行的时候是根据形参类型去读写形参变量的,而传递过来的实参会根据函数声明 里的形参类型进行类型转换。 如果声明的函数没有形参列表则传递的实参无法进行类型转换就直接把代表实参的二进制码直接复制给形参。 如果实参和形参的数据类型不对,而函数运 行时又是根据自己的形参类型去读取形参变量的,所以就会发生读取出错,严重的会使程序停止。 声明返回值类型是把数据自动转换成相应的类型才返回给调用者。 声明函数的返回值和定义函数时的返回值不一样编译时会提示类型出错。 在使用前没有声明或者声明的函数没有声明返回值则编译时默认的返回值都是int型,若该函数定义时声明的返回值不是int型,编译器就也会提示类型错误。 3)大多数C语言实现都是通过函数main的返回值来告诉操作系统该函数是否执行成功。 一般是返回0表示成功,返回其他值则表示失败。 如果main中没有return0;返回的是一个int型数值,但是不能保证值为0。 4.2局部变量和全局变量 理解与测试: 1)全局变量也叫外部变量,它是定义在函数外部。 它不属于那个函数,而属于一个源程序文件。 局部变量是定义在函数部,可以在函数的声明中定义,也可以在一个语句块中定义。 变量的作用围都是相对于同一层次来讲的。 例如函数有声明变量,在函数的语句块又有定义相同名称的变量,则起作用的是语句块的变量,虽然它们名称相同但是互不影响。 如下面的代码: intk=10; voidf(inti) { intk=i; k++; printf("%d\n",k); } main() { intk; printf("%d\n",k); f(10); for(k=0;k<5;k++) { intk=5; printf("%d\n",k); } getch(); } 输出结果是: 1692 11 5 5 5 5 5 2)静态局部变量没有初始化的值为0。 静态全局变量可以将作用围限制在一个程序文件模块中。 3)如果要引用另一个源程序文件的全局变量我们可以在本源程序文件中使用extern声明 另一个源程序文件的变量。 就可以从“声明”开始的地方合法地使用该全局变量了。 其一般格式如下 extern(类型名)变量表; 其中类型名可以省略。 4.3部函数与外部函数 在一个文件模块中要调用另一个文件模块的函数是,需要对函数进行外部声明。 外部声明的一般格式为 extern函数类型函数名(参数表说明) extern表示声明的函数是外部函数,它定义在其他文件模块中。 部函数也称为静态函数,使用部函数可以避免各文件模块函数的干扰,使不同的人可以分别编写不同的函数,而不用当心所用函数是否与其他文件模块中的函数重名。 4.4函数的递归与迭代 理解与测试: 1)在调用一个函数的过程中又直接或间接地调用该函数本身,称为函数的递归调用,带有递归调用的函数也称为递归函数。 递归其实包括两个过程,一个是递推的过程,函数不断调用自己知道边界。 另一个是回归过程,到了边界后一层层把值返回。 递推的边界也叫递推出口,我们设计递推函数时一定要有递推出口,不然会进入死循环。 2)递推函数的设计首先要找到事物存在的递推关系。 我暂且下一个定义: 一个事物包含跟他同概念的事物。 例如一根木棍截取一部分还是一根木棍,只是长度变小了。 这种包含关系也是这样,被包含的事物概念没有变化只是数量不同而已。 我们要处理一个事物的规律,我们直接按这个规律去设计,在设计的过程中我们可以直接使用我们这个还没设计完的函数(提前使用)。 例如要设计个程序把一根木棍截成多截, 我们可以这么想,要把木棍截成多截只需要分两步,第一步把木棍截取一截,第二步把剩下的截成多截。 因为剩下的还是一个木棍那么就可以调用函数自身来处理。 但是我们没法理解它怎么能自己用自己呢,其实可以理解为里面使用的是它包含的跟它同概念函数而已,由于他们都是一样的规律所以就不用再去设计一个函数了,实际上他们确实不是同一个函数,只是同概念,因为在真正执行的过程中是利用了第一个函数的规律在再运行了一个相同概念的子函数,没必要去编写n个同概念的函数。 如下图的包含关系。 3)下面用几个实例来做说明 求n! : ([n! =1n=0,1][n! =n*(n-1)! n>1]) 分析: 像这种有递推公式的就有很明确的递推关系。 我们的目的是要求n! ,我们只需 要两步,第一步是把n! 看成n*(n-1)! 。 第二步求(n-1)! 。 由于n! 和(n-1)! 都是求某一个数的阶乘,所以调用函数本身来处理(n-1)! 。 我们的思路知道此为止,不要在一直想下面怎么处理,程序会按我们这思路自己往下递推的。 程序设计: f(intn) 一、if(n==1||n==0)n! =1;//设计边界 二、n! =n*f(n-1);〃利用跟被包含的下一个同概念函数计算 〃这里千万不要感觉f()没有写完就不能用,其实递归函数方便的〃就可以随便大胆的调用“自己”。 代码如下: doublefact(intn); doublefact1(intn); main() { intn; printf("Inputn: "); scanf("%d",&n); printf("%d! =%.0f\n",n,fact(n)); printf("%d! =%.0f\n",n,fact1(n));getch(); } doublefact(intn) { doublef; if(n==1||n==0)//递归函数出口 f=1; else f=n*fact(n-1); returnf; } 汉诺塔问题: 分析: 要把A上的n个盘借助C搬到B上,我们的只需要两步。 第一步是把n-1个盘搬到C上,再把A上的最后一个盘搬到B。 第二步是把C上的n-1个盘借助A搬到B上。 由于把n个盘和把n-1个盘的规律相同,所以可以直接调用函数本身。 代码如下: voidhanio(intn,chara,charb,charc); main() { intn; printf("Inputthenumberofdisks: ");scanf("%d",&n); printf("Thestepsfor%ddisksare: \n",n);hanio(n,'A','B','C'); getch(); } 九连环解法: 分析: 我们这边设计是可以解n连环的程序。 九连环有个规律是要解或上第n个环必须 解去n-1个环。 我们解n个环只需要分成两步来处理,第一步就是把解下1个环。 第二步就 是把剩下n-1个环用解n个环的函数来处理。 解一个环的过程是些解下n-2个环,再把第n个环解下,再安上n-2个环(剩下部分要都是未解开的环)。 安n个环的步骤过程和解n个环的步骤过程相似。 代码如下: #include"Stdio.h" #include"Conio.h" staticintupstep=0; staticintdownstep=0; voidUpRing(intn);/*加上函数说明,否则编译将会出一点小错误*/ voidDownRing(intn)/*下环的逻辑就体现在这里*/ { if(n>2)DownRing(n-2); printf("DW: %d\t",n);++downstep; if(n>2)UpRing(n-2); if(n>1)DownRing(n-1); } voidUpRing(intn)/*上环的逻辑则体现在这里*/ { if(n>1)UpRing(n-1); if(n>2)DownRing(n-2); printf("UP: %d\t",n);++upstep; if(n
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 语言 基础 学习 笔记