C语言总结

来源:互联网 发布:在淘宝卖梨膏 编辑:程序博客网 时间:2024/06/06 04:22

//C语言语法复习

{   //if语句(表判断)   与的用法   评分系统

#include<stdio.h>
int main()
{
double a=0;
printf("请输入分数:");
scanf("%lf", &a);//这里的lf中f前面的不是数字一,而是英文字母l且为小写,如果把double类型变成float类型就要去掉l
if(a>=0 && a<=20)//if后的括号没有';'号,都引导了一个循环体,如果后接一个;代表执行语句是空语句,就是什么事都不做
{
printf("成绩等级为:D");
}else if(a>20 && a<=40)
{
printf("成绩等级为:C");
}else if(a>40 && a<=60)
{    
printf("成绩等级为:B");
}else if(a>60 && a<=80)
{
printf("成绩等级为:A");
}else if(a>80 && a<=100)
{
printf("成绩等级为:S");
}else if(a>100 && a<=120)
{
printf("成绩等级为:ss");
}else 
{
printf("请输入正确的分数");
}
return 0;
}
//注:以上if语句的嵌套写法挺好,这里千万不要用缩进式,不然太难看了。。
//另外if...else的写法else就排除了前面if的情况,所以以上程序的条件表达式可以简化,试一下~


//相对于if有switch语句(开关语句),也是一类条件语句,后面将涉及
}


{   //字符串输出   %s表示字符串的形式   []的用法   第6课  
char a[7]="zixue7";   //符号[]中放入输入字符的位数后加=号  这里[]中是7(代表7位)而""中是6位,因为还有个'\0'被隐藏了,
 //它是字符串结束标志,如果不是7,那就会溢出,编译错误,""这个符号后加;
 //本定义等价于char a[7]={"zixue7"};还可以把每个字符分开,但分开的话一定要加'\0'
 //字符串字面值常量是隐含了'\0'的了
 //如不想计算字符个数并会用字符串字面值常量初始化字符数组,可以不写明元素个数
printf("%s", a); //%s占位符表示输出字符串


//由于字符串是以'\0'作为结束标志的,对字符数组进行逐个元素操作时常常需要在末尾加上'\0',显得麻烦,可以定义static局部变量


//static局部变量和全局变量都是在静态存储区开辟内存空间的,静态存储区存储的数据都是数值0,即不初始化变量的值都是0,
//若变量是字符型,则存储'\0',为指针类型则为NULL,为整型类型则为0等,他们的数值都是0


//所以定义static局部字符数组所有的元素都是'\0',不过直接给数组初始化为空字符串,如:char p[10]="";更好,,它等价于:
//char p[10]={'\0'};类似int a[10]={0};就是把每个元素都赋为0了,所以p的定义使每个元素都是'\0'了。


//此处提出static字符数组更主要的是引出static局部变量的概念,百度具体了解下吧,另static关键字还有其他功能,后面会具体分析
}


{ //常量的字符串输出和数字输出     #define语句和const语句的用法  第6课 
#include<stdio.h>
#define ZIXUE "www.zixue7.com"    //ZIXUE最好是大写形式,他是一个宏定义
#define DIGIT 1223 //定义对应数字常量的宏定义格式如左
int main()
{
ZIXUE=100;//这个是语法错误,因为#define定义的是常量,而常量不可被赋值
printf("%s", ZIXUE);
return 0;
}


//注:宏定义按照习惯写法,宏的名字全部为大写字母,对于宏定义和头文件包含都是在编译前进行,该过程叫预处理
//由预处理器执行,宏定义是把程序中所有宏名字代替为宏定义中宏的内容,所以有时会引起逻辑错误,宏定义规定
//常量时都可以用const变量代替,然后对const变量操作,便避免了一些情况的逻辑错误,具体百度。。


//关于const变量的内存分配时间有些知识点,const修饰指针也常常使初学者难以辨清,后面会做具体分析


//宏定义可以带参数,即带参宏,它可以代替一些规模较小的函数,而且不需考虑类型,考虑下面程序
//该程序的MAX带参宏功能类似于C++的函数模版
#include<stdio.h>
#define MAX(a,b) (a>b?a:b)
int main()
{
int a=1,b=2;
double c=1.1,d=2.2;
char e='a',f='b';
printf("%d\n%g\n%c\n",MAX(a,b),MAX(c,d),MAX(e,f));
return 0;
}
//注:?:是条件运算符,如果暂时不了解可以做一下记录,后面会提及


//此外宏定义可以作为条件编译的条件,此时甚至只需要定义一个宏名而不需对应内容,关于宏定义会在后面较
//完整地单独解析,条件编译则是另一个内容,都会讲,敬请期待。。


#include<stdio.h>
int main()
{
const int a=12;   //在int前加const定义一个常变量(或称const变量)
a=90;     //这个是语法错误,因为const定义的是常变量,而常变量不可再次赋值
printf("%d", a);
return 0;
}
//注:const变量一定要初始化,因为它不可以做赋值运算
}


{    //各种占位符     第7课


/* %d  整型数       在进行除法运算中若两个操作数都是整型类型,运算的结果只保留整数部分,
小数部分不进行四舍五入如3/2的结果是1
%f  浮点数 小数形式
%o  八进制无符号 如果想在输出的结果上显示是多少进制就在%后加#
%u  无符号数
%c  字符型
%s  字符串
%x  十六进制     如果想在输出的结果上显示是多少进制就在%后加#   %X可以表示成大写字母形式
%e  浮点数的科学记数法   %E可以表示成大写字母形式*/


//注:表示浮点数的占位符有:%e,%f,%g。%e格式是科学记数法,%f是小数形式,%g则是两者选一,由系统
//根据具体数据选择表示方法,所以一般不做特殊要求时,用%g即可



{ //格式化输入   用scanf函数来输入一段数字和字符串    第8课
#include<stdio.h>
int main()
{
int a=0;
char s[123]="";    //这里的""号要为空,用空字符串进行了初始化,即所有元素共123个都是'\0',
  //不信写个循环打印每个元素的ASCII码
printf("请输入数字和字符串:");
scanf("%d%s", &a, &s);   //用&号标明输入的数字要存入哪里,需要注意的是如果只是输入字符串,也就是只有%s可不加&号
printf("您输入的数字是%d\t您输入的字符串是%s",a,s);   //这里用到的\t是一种空格符,相当于tab键,打印8个空格
return 0;
}

//以上的程序接收字符串时只能接收连续的一段,如不理解,考虑一下实验
//请在控制台内输入"Iamagirl oraboy"
#include<stdio.h>
int main()
{
char str[100]="";
printf("Input your content;\n");
scanf("%s",str);
printf("Your content is:\n%s\n\n",str);
return 0;
}

//所以若想要接收一整段的字符串,中间可以写入空格,tab键等,仍以换行符作为结束标志,后面会提到功能写法
}


{ //格式化输入和输出的整理    Ctrl+d的用法  %20d表示每一竖列的输出距离, 坦克大战的游戏目录  第9课
#include<stdio.h>
int main()
{
printf("%d    %d     %d\n", 1213, 12364, 123745);   //用鼠标点击这行,然后按Ctrl+d就可以在下面复制出同样的几行
printf("%d    %d     %d\n", 12223, 188234, 127345); //复制的结果, 这里已经修改过了,里面的数字原来的应该是123,1234,12345
printf("%d    %d     %d\n", 12133, 12634, 1235745); //这里面只能放数字!(其实整形变量也可)
printf("%d    %d     %d\n", 123, 125434, 1209345);
/*这种方法输出的数字不整齐,试试,应该在%d之间,也就是%后加一些数字比如%20d, 这里的20表示每一竖列相距20个字符的长度。默认
 右对齐,在20前还可以加入-号表示左对齐还可以在%10d的10前加入0表示不足十位就用0补充.注意此时不能再加-号了*/
return 0;
}
}

//多个格式字符串,则格式字符串间可以换行,相当于一个长长的格式字符串写在一行。
//请运行以下程序试试效果
{ #include<stdio.h>
int main()
{
printf("                        欢迎来到坦克大战\n"
  "                        ================\n"
  "                         1.开始游戏\n"
  "                        ================\n"
      "                         2.菜单\n"
      "                        ================\n"
      "                         3.设置游戏\n"
      "                        ================\n"
      "                        4.关于游戏\n"
      "                        ================\n"
        "                         5.退出游戏\n");
  return 0;
}
}


//注!一个字符串不可以中间断开换行,想换行的话要在结尾加\并且之后不能有其他字符,空格字符也不行!
//然后字符串的内容由第二行的最左位置算起,如以下程序
{ #include<stdio.h>
int main()
{
printf("WHAT A FUCKING\ 
DAY!!\n");
  return 0;
}
}
//其实\的作用就是将本行的后面的空区域忽略,也可以把一个变量名分成两行,同以上例子道理,其他代码也一样,试一下~


{ //用整数加法计算数据     第9课
#include<stdio.h>
int main()
{
int a, b, c;
printf("请输入两个整数:");
   scanf("%d%d", &a, &b);  //    &这个符号莫忘
c=a+b;   //赋值表达式:先算=右边的式子,再将结果复制给左边的变量,所以=左边一定是个变量且可以被赋值
//注:=左边一定是一个左值,=右边可以是左值或右值。两者的概念,什么做左值,什么做右值百度吧
printf("你输入的结果为:%d", c);
return 0;
}
}
//注:scanf的格式字符串规定了用户输入内容的格式,上面程序的格式字符串允许用户输入数据,每个数据以空格符隔开
//空格符包括空格,tab,换行,如果是"%d,%d"的格式字符串则要以,为分隔符,试试~


{ //用浮点数加法计算数据  强制类型转换    第10课
#include<stdio.h>
int main()
{
float a, b, c;   //当定义的是float类型的浮点数的时候%f不变,如果是double类型就要在%f之间加l(可视为long float记忆)
//(l是英文字母不是数字1)
printf("请输入两个小数:");
   scanf("%f%f", &a, &b);  //还可以在%f之间加.1表示保留一位小数,但输入时就要以这个格式
c=a+b;   //c=a+b这个语句在scanf函数下,因为此时a,b才有实际意义的值
printf("你输入的结果为:%f", c);
return 0;
}
}
//注:scanf的格式字符串内的占位符常见的有:%d %f %lf %c %s等但%s用于读取字符串时字符必须是连续的,即遇到空格符即确定字符串
//如空格和tab键都会确定一个字符串,至于enter就是停止一轮输入了。


//于是有疑问:可否读取带空格符的字符串,请考虑以下程序
{
#include<stdio.h>
int main()
{
char str[30]="";
printf("Input a string:\n");
//下面是一个特殊的格式符,它表明遇到'\n'前都会读取内容
//(enter之后会有一个'\n'存入缓冲区的),但遇到'\n'后,
//'\n'会留在缓冲区,请考虑第三个程序。
//还可以将格式符的\n变为#或其他字符,即以'#'为结束标志,
//这样可以读取换行符了,考虑第二个程序
scanf("%[^\n]",str);
printf("Your string is:\n%s\n",str);
return 0;
}


#include<stdio.h>
//输入一段文段,可以空格可以换行,并以#结尾enter后试试
int main()
{
char str[30]="";
printf("Input a string:\n");
scanf("%[^#]",str);
printf("Your string is:\n%s\n",str);
return 0;
}


#include<stdio.h>
//输入一段文段,可以空格可以换行,并以#结尾enter
int main()
{
char str[30]="",c;
printf("Input a string:\n");
scanf("%[^#]",str);
printf("Your string is:\n%s\n\n",str);
//此处读取了缓冲区残余的#
c=getchar();
printf("c:%c\n",c);
return 0;
}

//注:以上写法规定了这样的格式:从缓冲区读取字符,直到读到结束标志字符便停止读取,但作为结束标志的字符并没有被读掉,留在缓冲区
//    以上的c便接收了这个残留字符。
//另:结束标志字符可以为其它的字符或EOF,试着写一个这样的程序,字符数组可以接受任何字符(包括空格,tab,换行符等),输入结
//    尾为换行后ctrl+z(该组合键在windows下输入EOF,在unix下组合键为ctrl+d),结束输入,再打印输入内容  
}

{ //强制类型转换
#include<stdio.h>
int main()
{
printf("%f", (double)5/2);      //可以去掉(double)这个语句,并改5为5.0,但改为5是不行的,因为占位符和计算结果类型不匹配
//另,这里只是对5做显示转换,而非5/2,如需要要加括号,除号还可以改成别的运算符号
return 0;
}
//关于强制类型转换(或称显示类型转换)也有较多的知识点,后面会讨论
}
  
{
#include<stdio.h>
int main()
{
double a=0;//这里的double可以改成float但是下面的f前面就对应不加l(字母l),
  //即占位符与变量类型的匹配
scanf("%lf",&a);
printf("%f",a/2);   //可以在%后面加.2来规定显示两位小数,-也可以加,左对齐,还有规定字宽,试试这个:%-10.5f   
return 0;       
}
}


{ //取余运算  自增 自减     第11课
#include<stdio.h>
int main()
{
printf("%d", 5%2);//取余符号%,必须是整型才能进行取余运算,浮点数编译不过的,试一下
 //取余即除法取余数
return 0;
}//结果是1
//注:关于取余和除法:
//    ①若两个操作数都为正数,商和余数都为正数
//    ②若两个操作数一个为正一个为负,则商和余数的符号有编译器决定
//    ③若两个操作数都为负数,商为正数,余数为负数(其绝对值分别为被除数和除数的绝对值做求商和取余运算的结果,自己实验下~)


#include<stdio.h>
int main()
{
int a=0;
printf("%d", a++);//先把数传给printf函数再自加,如果把a++改为++a先自加再传给printf函数此时结果为0
printf("%d", a);//输出结果为1
return 0;
}

#include<stdio.h>
int main()
{
int a=0;
printf("%d", ++a+12);//此时++a的值为1,也就是1+12了
printf("%d", (a++)+12);//此时a++的值为1,也就是1+12,因为这里的a已经被上面的语句赋值为1了,
  //而++则在下面的语句中体现,这里()是为了不会被误读
printf("%d", a);//a的值变成2因为上面的a=1,然后由于++,a的值变为1+1=2,所以输出了2
return 0;
}


//注:考虑:int a=1,b=(a++)+(++a);b的结果是什么呢?答案是要看编译器。因为C标准并没有规定双目运算符
//的左右两边操作数的计算顺序,所以运算顺序由开发者设计的编译器规定。


//双目运算符即需要两个操作数的运算符,如:+-/*%...单目运算符如:位取反~,逻辑非!,取地址&,解引用*...
//还有一个唯一的三目运算符?:

#include<stdio.h>
int main()
{
int a=0;
4++;  //这是不对的,因为自增运算(自减同理)只能用在整数变量上(如a++),而这是常量
printf("%d", ++a);
return 0;
}


//可以再考虑以下程序,请在推测程序结果后运行验证,作为复习
#include<stdio.h> 
int main()
{
int a=5,b,c;
b=a++;
c=++a;
printf("b:%d c:%d\n",b,c);
return 0;
}
//前置自加符号和后置自加符号,如++a和a++,单独成语句时并无差异,但作为子式放在一个表达式或函数内则有差别
//如:b=a++;和b=++a;前者先赋值再自加,后者先自加再赋值,但也有特殊的:?:和逗号表达式,两者提及时会给出实
//例分析。。另:自减运算符与自加运算符同理
}


{ //b=(1,2,3)的用法  自动类型转换   第12课
#include<stdio.h>
int main()
{
int a=0, b=0;
b=(12,2,6);//这里b的值赋为6,6为=右边逗号表达式的结果
a=(a=12,a=a+1,a);//这里先把12赋给a,再把a的值12加1并作为最后结果传给a,结果a为13,最后的将a赋给=左边的a
printf("%d", a);
return 0;
}
//注:逗号表达式,确实是一种表达式,不是语句,它的计算过程为从左到右计算各个子命令,把最后一个子命令的结果
//作为整个逗号表达式的结果。


//另外特殊地考虑以下程序结果,并运行检验猜想
    #include <stdio.h>
int main()
{
int a=0,b;
a=(a++,b=a,a+1);
printf("%d %d\n",a,b);
return 0;
}


//隐式类型转换
#include<stdio.h>
int main()
{
int a;
char b;
b=1.5;
a=b;//在从右到左的赋值中,浮点数变成整数时丢失了0.5这个数据,不会进行四舍五入(隐式类型转换)。
printf("%d", a);
return 0;
}  

#include<stdio.h>
int main()
{
int a;
a=98;
printf("%c", a);//按字符的形式输出成'b','b'对应的ASCII码就是98,%c用来输出一个字符
return 0;
}
//隐式类型转换也有相关的知识点,一般地它发生在表达式(赋值表达式较特殊),函数传参,函数返回等
//并非所有类型都可以隐式转换为其他类型,如复合类型(指针,数组等)需要显示转化,后面会具体分析。。
}


{ //循环语句    第13课
int a=0;
while(a<=100)                
{                    
printf("%d", a);
a++;//while 语句在执行完某个命令后比如 printf("%d", a);需要修改a的值
   //目的是为了打破while函数的条件,不然会出现死循环
}
}


{ //do-while循环语句      第14课
#include<stdio.h>
int main()
{
int a=0;
   do 
{
scanf("%d", &a);
        }while(a<=100);//只要条件满足就不停止循环如输入20,如果输入120就会退出循环,执行下一句
        printf("%d\n", a);//输出a的值
        return 0;
}  
}


{ //将小写字母变为大写字母
#include <stdio.h>
int main()
{
        char ch;   
        do//先执行后判断
{
scanf("%c", &ch);
        }while(ch<'a' || ch>'z');//这里的while语句中,括号里面的条件不满足才会执行下面的语句
        printf("%c\n", ch-('d'-'D'));//'d'-'D'的十进制值为32,别的字母也是这样如'a'-'A'
        return 0;
}
    //(任意字母字符的小写形式的ASCII码)-(其字母字符的小写形式表示的ASCII码与大写表示的ASCII码的差)
// =其字母字符的大写形式的ASCII码
// 字符的数值即为字符对应的ASCII码,所以有上述程序的转换表达式


// 另:还可以通过类似思路通过对ASCII码的运算来把一个字符转化为另一个字符,如:数字转为小写字母等
// 属于思路范畴,通过查ASCII码表可获取具体转换表达式
}   


{ //第15课   for循环语句  乘法口诀表
/*  for(初始化;条件;改变数据)//for语句后面没有分号,直接加分号循环体为空语句
{
循环体;
}
*/
 
//例如:  
#include<stdio.h>
int main()
{
int a=0;
for(a=0;a<127;a++)  /*先执行初始化式和条件,等下面的循环体执行完一个回合后再执行改变的条件,重要的是,当a为126时,
126<127是可以被执行的,所以执行完下面的循环体,并通过a++加上了1时,a为127,此时a不再小于127而是等于了,所以循
环体不再执行,但a已经是127了,如果在循环体后加个printf语句输出a的值,那么结果为127  */
{
printf("%5d", a);
}
printf("%5d", a);
return 0;
}  


//上面的代码实际等价于:
#include<stdio.h>
int main()
{
int a=0;
while(a<127)  
{
printf("%5d", a);
a++;
}
return 0;
}

//也等价于:
#include<stdio.h>
int main()
{
int a=0;
for(a=0;a<127;printf("%5d", a),a++);  //循环体直接带入改变条件式子中,但括号后加了';'号,即后接空语句
 //而printf的括号后';'改为',',即作为了逗号表达式的子命令 
return 0;
}  
//注:三种循环语句中最强的是for语句,其它两种都可以被for语句取代,能够灵活使用for适应大多数循环是必须掌握的技能


//九九乘法口诀表:
#include <stdio.h>
    int main()
    {
int cs, bcs;
for(bcs=1; bcs<10; bcs++)
{
for(cs=1; cs<=bcs; cs++)
{
printf("%d×%d=%d\t", cs, bcs, cs*bcs);
}
printf("\n");
}
return 0;
}
}           


{ //第16课  与或非
// &&  与  条件1和条件2同时满足值为1,否则为0
// || 条件1和条件2中有一个满足值为1,否则为0
// ! 把条件满足的变为不满足,反之同理,即非零操作数变为0,数值为0的操作数变为1


//注:C语言中,非零值表示逻辑真,0表示逻辑假,以上三个逻辑运算符组成的逻辑表达式值为0或1
//另:关系表达式的结果也是逻辑值,也是0和1,关系运算符:> >= < <= == !=


//注:关于运算符必须弄清的概念是优先级和结合性,并且要记住必要的内容,具体建议后面会分析
//先提出几点:除了逗号运算符,赋值运算符优先级最小;单目运算符优先级大于双目运算符等等

//下面是本课例子
#include<stdio.h>
int main()
{
int a=2;
while(a>0 && a<3)
{
printf("%d",a);
}
return 0;
}

#include<stdio.h>
int main()
{
int a=2;
while(a>0 || a>1)//实际上就是a>1
{
printf("%d",a);
}
return 0;
}

#include<stdio.h>
int main()
{
int a=2;
while(!(a>10))
{
printf("%d",a);
}
return 0;
}
//可以实验一下输出检测逻辑表达式和关系表达式的结果,虽然并没有太大实用性



{ //第17课 赋值运算+=,-=,*=,%=,/=
int a=0;
a=a+4; //这里a=a+4实际上就是a+=4,这可用于加减乘除和取余运算(位运算等也可,再说吧)
printf("%d", a);


//注:表达式都是有运算结果的,赋值表达式的结果是赋的值,如a=5的结果为5


//由于初学者可能会混淆=和==,而=表达式的结果却是赋的值,所以会引起较严
//重的不易察觉的逻辑错误,一定要慎之又慎


//=是右结合性,如a=b=c=0;先算c=0,表达式结果为0,再将结果0赋给b,以此推理分析
//结合性的概念可以自行百度或群内讨论
//此例子若用于数组赋值,可以得到多个相同数据的数组,考虑下列程序
#include<stdio.h>
int main()
{
int a[10],b[10],c[10],d[10],i;
printf("Input ten integer:\n");
for(i=0;i<10;i++)
{
scanf("%d",&a[i]);
b[i]=c[i]=d[i]=a[i];
}
for(i=0;i<10;i++)
printf("%d %d %d %d\n",a[i],b[i],c[i],d[i]);
return 0;
}
//于是得到的每个数组可以作为一项实验原数据用于一项操作了,如用于多个排序算法的实验
}


{ //第18课 循环嵌套
#include<stdio.h>
int main()
{
int a=0, b=0;
for(a=0;a<3;a++)      //先执行外面的for语句再执行里面的
{
for(b=0;b<2;b++)
{
printf("*");//*号外面要加""号
}
printf("\n");//不能把\n写在*号后面因为要等上面那个for语句执行完后再换行
}
return 0;
}


//循环嵌套很常用,其中一个例子是对二维数组的元素操作二层循环嵌套时内层break了并不能
//跳出外层循环,如果想要在内层循环满足条件就跳出循环,可以设立一个标志变量,在内层
//满足条件时做标志并跳出内层循环,外循环则要有判断改标志的语句,考虑下面程序
#include<stdio.h>
int main()
{
int arr[5][5]={0},i,j,flag=0,zerocount=0;
arr[3][3]=1;
for(i=0;i<5;i++)
{
for(j=0;j<5;j++)
{
if(arr[i][j])
{
flag=1;
break;
}
zerocount++;
}
if(flag)
break;
}
printf("The number of zeros before one is:%d\n",zerocount);
return 0;
}


//但若很多层循环嵌套又如何从最内层跳出最外层循环呢?还是用标志变量吗?可以,,但太麻烦
//这个时候用goto语句最好。
//goto语句破坏了程序的合理流程,着重指出不建议过多使用,它的所有功能都可以用其他语句实现,
//关于goto运用的较好例子,目前笔者只想得到跳出嵌套循环这个运用
}


{ //第19课  if语句
if(/*条件*/)
{
//满足条件要执行的语句,如果语句不止一行{}号必须要,如果只有一行就可要可不要
}

//下面是例子:
#include<stdio.h>
int main()
{
char a;
printf("请输入小写字母");
scanf("%c", &a);
if(a>='a'&&a<='z')//条件满足时执行花括号里面的语句,执行完后再执行花括号下面的语句,不满足就直接执行花括号下面的语句
{
printf("你输入的%c是小写字母", a);
}
return 0;
}
}


{ //第20课     if...else 
#include<stdio.h>
int main()
{
char a;
printf("请输入小写字母");
scanf("%c", &a);
if(a>='a' && a<='z')
{
printf("你输入的%c是小写字母", a);
}
else 
{  
//这里多了个else,后面还可以接if语句,记住不管后面接没接if语句若执行语句不止一个,都要在后面加花括号,
//else 可以引导不满足条件要执行的语句
printf("你输入的%c不是是小写字母", a);//其实if或循环体只有一个语句时,花括号可以不写,但缩进式写法是必须有的
}  //除非你想挨骂,反正我肯定骂娘。。
return 0;
}
}


{ //第21课  条件运算符  ?:
#include<stdio.h> 
int main()
{
int a=0;
printf("请输入一个数字");
scanf("%d", &a);
(a<=10 && a>=1)?printf("你输入的数在1与10之间"):printf("你输入的数不在1与10之间");//注意符号的变化
//语句的基本结构为:(条件表达式)?满足条件时执行的命令: 不满足条件时执行的命令 
return 0;
}


//注:?:是三目运算符,其实可以看成等价于if...else流程语句而非一般的表达式,判断以下程序运行结果并运行:
#include<stdio.h> 
int main()
{
int a=0,b;
a++>0?b=a:b=1;
printf("a:%d b:%d\n",a,b);
return 0;
}
}


{ //第22课  continue  break语句
#include<stdio.h> 
int main()
{
int a=0;
while(a<100)
{
a++;
if(a%2==0)
{
continue;  //如果满足if语句中的条件执行到continue时,本轮循环就会被跳过,进行下一轮循环
}
printf("%d\t", a);
}
return 0;
}


#include<stdio.h> 
int main()
{
int a=0;
while(a<100)
{
a++;
if(a%2==0)
{
break;  //如果满足if语句中的条件并执行到这里时,会跳出整个while循环,并执行之后的语句
}
else
{
printf("%d\t", a);
}
}
printf("www");
return 0;
}
//break和continue语句的好处在于满足编程者的目的后就可以跳过不必要的语句,这样节省了许多时间
}


{ //第23课  分支和跳转  switch和break
#include<stdio.h> 
int main()
{
int a=0;
scanf("%d", &a);
switch(a)//这里的括号中是条件表达式
{
case 1: printf("一"); break;//这里的break是跳出switch语句,如果去掉就会输出“一二”
case 2: printf("二"); break;
case 3: printf("三"); break;
default:printf("请输入1-3的数"); break;//default用在以上条件都不满足的情况
}
return 0;
}
//注:break只用于switch语句和循环结构


#include<stdio.h> 
int main()
{
int a=0;
scanf("%d", &a);
switch((int)a/10)//这里的括号中必须是整数
{
case 0:printf("成绩为D级");break;//如果输入10,那么10/10=1,符合case 1的情况但是它会输出下面一句的内容
case 1:                          //没有break语句会导致满足case 1的情况后会在执行完当前case 1语句后执行case 2的语句,
//一直这样下去,直到遇到break为止
case 2:printf("成绩为C级");break;
case 3:
case 4:printf("成绩为B级");break;
case 5:
case 6:printf("成绩为A级");break;
case 7:
case 8:printf("成绩为S级");break;
case 9:
case 10:printf("成绩为SS级");break;
default:printf("请输入1-100的成绩"); break;//default用在以上情况皆不满足的情况
}
return 0;
}
}
//注:switch内一旦满足某个条件并且没有break语句的话,case判断都会忽略,运行case后的命令,default包含在内


{ //第24课   goto语句


//注:goto语句编写的程序完全可以由其它流程结构来编写,goto语句破坏了程序的合理流程
//使用goto转来转去的太恐怖太难看懂了~尽量不要用,会被骂。
//用goto的一个好例子:多重循环嵌套时,从较内层跳出最外层循环


#include<stdio.h> 
int main()
{
goto a;  //跳转语句,说明要跳到标签a后的语句
b:  //跳转的坐标
printf("a"); //所以左边程序会出现死循环的情况
a: //但却不能加break,会编译错误
printf("www\n");
goto b; //要调到标签b后的语句
return 0;
}

}


{ //第25课   字符输入输出和确认   getchar    putchar 
//printf("%c\n", getchar());


#include<stdio.h>
int main()
{
char ch;
ch=getchar();//获取一个字符,并可以在输入Enter键后回显,这属于行缓冲输入
while(ch!= '#') //当遇到#时停止循环
{
putchar(ch);//输出一个字符
ch = getchar();//在读取一个字符
}
printf("%c\n", ch);//这句是专门给输出#用的                
return 0;
}        

//其实getch是从键盘直接接收字符,并不存入缓冲区,头文件是<conio.h>,另外getche类似,但前者不显示字符,后者显示
#include<stdio.h>
#include<conio.h>
int main()
{
char ch;
ch=getch(); //getch可以接收一个字符,由于是非缓冲输入,所以在键入一个字符
//(连Enter键都不需要按,因为如果是需要输入Enter才能回显的话,就是行缓冲输入了)后输出一个字符
printf("%c\n", ch);//注:不加这一句不会显示字符的,试一下
return 0;
}
}
//在直接打开.exe文件时会一闪而过,不信试试,所以用getch可以让程序最后按一下键盘才关闭,思路自寻


//注:getchar返回值为整型int,原因是他要能够接收EOF(END OF FILE),EOF是一个宏定义,值是-1,如果
//类型为char并不好,因为C标准并不规定char类型一定是signed类型,可能为unsigned char类型,在windows
//下输入EOF的方法是ctrl+z,unix下是ctrl+d,可以用int类型的a接收getchar返回的值,输入时ctrl+z输入EOF,
//再打印a,会得到-1,试一下


//如果要接收到所有可打印字符,用EOF作为判断标志是可以的,但在VC下输入EOF有点BUG,百度吧。。建议换个编译器实验
//此外利用此知识点可以写一个函数,用户输入句子,enter后输入该句子,然后可以继续输入句子,反复循环,直到输入EOF


//getchar函数涉及到的一个很重要的概念是缓冲区,考虑以下程序
//请运行两次,第一次输一个字母,第二次输两个字母,
#include<stdio.h>
int main()
{
int c;
printf("Input a character:");
c=getchar();
putchar(c);
printf("\nInput another character:");
c=getchar();
putchar(c);
putchar(10);
return 0;
}
//再考虑下面的一个程序
#include<stdio.h>
int main()
{
int c;
printf("Input a string:\n");
c=getchar();
printf("The first character of your string is:");
putchar(c);
while(getchar()!='\n');
printf("\nInput another string:\n");
c=getchar();
printf("The first character of this string is:");
putchar(c);
putchar(10);
return 0;
}
//用getchar函数时,按下enter后输入的内容都存在了缓冲区,并不会直接赋给变量,然后getchar每被调用一次
//从缓冲区读取一个字符,并返回对应ASCII码(EOF是-1),据此以上的程序可以分析了。注:enter后换行符也
//会存入缓冲区。


//此外,scanf也会先将内容存入缓冲区,考虑以下程序
//输入两个数字,然后enter
#include<stdio.h>
int main()
{
int a,b;
scanf("%d",&a);
printf("a:%d\n",a);
scanf("%d",&b);
printf("b:%d\n",b);
return 0;
}
//另外关于scanf很严重的一点如:如果格式字符串要求输入一个整型然而用户输入一个字母,那么这个字母将不可
//能存到对应变量,而且这个字母会一直存在缓冲区内,如何判断scanf读取错误呢?其实scanf有返回值的,他返回
//成功接收数据的变量的个数。考虑以下程序
//输入一个字母再enter
#include<stdio.h>
int main()
{
int a;
printf("Input a digit:");
while(scanf("%d",&a)!=1)
{
printf("ERROR!Input a digit:");
while(getchar()!='\n');
}
printf("Your digit is:%d\n",a);
return 0;
}


{
//第26.5课 避免缓冲区输入或清空缓冲区的四种方法
//用户输入控制的四种方法:清空或避免缓冲区的四种方法


//程序目的:允许用户输入Y/N,Y则调用打印函数,N则结束程序
#include<stdio.h>
#include<conio.h>
#define FUN1
#define FUN2
#define FUN3
//#define FUN4


void Print()
{
printf("Do some works.\n\n");
}


//主函数:
int main()
{
char c,t;
printf("Input Y\\N to print or exit:\n");


//非缓冲输入方法
#ifndef FUN1
while(c=getche(),c!='Y'&&c!='N')
printf("ERROR COMMAND!Input again please:\n");
#endif


//getchar()函数清空缓冲区
#ifndef FUN2
while(c=getchar(),c!='Y'&&c!='N')
{
printf("ERROR COMMAND!Input again please:\n");
while(getchar()!='\n');
}
#endif


//fflush函数来清空缓冲区
#ifndef FUN3
while(c=getchar(),c!='Y'&&c!='N')
{
printf("ERROR COMMAND!Input again please:\n");
fflush(stdin);
}
#endif


//scanf的特殊格式字符串清空缓冲区
#ifndef FUN4 
while(scanf("%c%*[^\n]%c",&c,&t),c!='Y'&&c!='N')
printf("ERROR COMMAND!Input again please:\n");
#endif


if(c=='Y')
Print();
else
putchar(10);
return 0;
}
}


{ //第26课   指针
//指针就像是个路标,指针的值为指向的变量的地址而不是其中的值
}


{ //第27课   指针的简单用法
//定义指针名  例如:
int *pi;
char *pc;
double *pd;
//定义时赋值
int a=10;
int *pa=&a;
//定义后赋值
int a=10;
int *pa;
pa=&a;

//注:除非随后紧跟赋值语句,否则定义变量时一定初始化,指针的话如果没预先定义变量,可以初始化为NULL


//地址一般打印为十六进制数,占位符为%x,当然十进制也行。。下面用十进制
#include<stdio.h> 
int main()
{
char n='a';
char *pn = &n;  //*pn是保存的地址这个地址等于n 的地址
printf("n:%c, n的地址:%d, pn:%d, pn的地址:%d", n, &n, pn, &pn );
   //n:a, n的地址:2293583, pn:2293583, pn的地址:2293576,这里pn存的值是n的地址而不是n中的值,但pn变量对应另一个地址
return 0;
}


#include<stdio.h> 
int main()
{
int a=10;
int *pn=&a;
printf("a的值:%d, a的地址:%d, pn的值:%d, pn的地址%d, pn指向地址中的值:%d", a, &a, pn, &pn, *pn);
return 0;
}


#include<stdio.h> 
int main()
{
int a=10, b=20;
int *pn=&a;
pn = &b;   //pn的值不再是a的地址,而变为b的地址了
printf("%d\n%d",a,*pn);//这里的*pn是pn存储的地址指向的变量的值,而pn存储了指向的变量的地址,也是指针本身的值
return 0;
}
}


{ //第28课    字符串指针详解    指针所指的是字符串的首地址  
//注:其实字符串常量的类型是const char*


#include<stdio.h> 
int main()
{
printf("%d", "zixue");//其中"zixue"传的是首地址而不是zixue
return 0;
}


#include<stdio.h> 
int main()
{
char *str="www.zixue7.com";  //这里并不是把www.zixue7.com传给str而是把它的首地址传给str
printf("%s", str);   //这里输出了str存储的地址开始的对应的字符串
//如果把str变成*str,就会访问首地址的值,改成%d会输出它的ASCII码
//注:char其实是一类特殊的整型类型
return 0;
}

#include<stdio.h> 
int main()
{
char *str="www.zixue7.com";  
printf("%c", *str);   /*这里只输出了www.zixue7.com中的第一个字母w,*str是获取第一个字符的值,
str其实存储了字符串的首地址,*str为解引用*/
return 0;
}


#include<stdio.h> 
int main()
{
char *str="www.zixue7.com";  
printf("%c", *(str+5));   /*这里加5的作用是在原有的w的首地址加5,由于存储区域的每个单元的地址是连续的,
字符串的每一个字符按顺序存储在一段存储单元段的每一个单元内,所以可以通过
加减来访问字符串中不同的字母,其实是涉及到了指针运算*/
return 0;
}
//通常看到的包含字母的地址的值不是字母而是数字(十六进制)
}


{ //第29课 数组   数组下标从0开始
#include<stdio.h> 
int main()
{
//数组   就是一组同样类型的变量
//类型   数组名[数组大小]
int n[10]={1,2,3,4,5,6,7,8,9,10};   //这样就定义了10个类型为int的变量,=后面是数组的初始化式,如果只给前几个变量赋值,
   //那么后面的默认为0
printf("%d", n[0]);//这样输出了第1个数组元素,也就是1,如果方括号中的数字是4,那么输出第5个,
  //但方括号中的数字不能为10,因为10加1为11,超过了元素个数,
return 0;
}
//注:编译器并不会对数组地址溢出的元素做操作的错误写法做出报错提醒,但通常来说这样的写法都是不符合编写程序的人的目的的,
//一定要慎之又慎!因为只能自己控制!


#include<stdio.h> 
int main()
{
int n[10]={1,2,3,4,5,6,7,8,9,10};   
n[0]=1000;//这是对变量进行赋值,但每次只能给一个变量赋值,C里不能给数组总体赋值!
printf("%d", n[0]);//此时输出的结果变为1000
return 0;
}

#include<stdio.h> 
int main()
{
//如果想把10个变量中的值都输出来,可以用for语句实现,此时i的值是数组的下标
int n[10]={1,2,3,4,5,6,7,8,9,10};   
int i=0;
for(i=0;i<10;i++)
{
n[i]=i+100;
printf("%d\t", n[i]);
}
return 0;
}

#include<stdio.h>
int main()
{
const int me[]={1,2,3,4,5,6,7,8,9,10};//如左边格式定义数组,编译器会自动计算元素的个数
 //如果想避免个数计算错误可以不在方括号里面填写数字,但是要用到下面的语句
int i;
for(i=0;i<sizeof me/sizeof me[0];i++)//整个数组的大小除以单个元素的大小等于数组的个数
{
printf("%d\n", me[i]);
}
return 0;
}
//sizeof是一种运算符,一般后接括号,像是一个函数,括号内可以是类型名或某一类型的字面值常量或数组名,它的结果是对应类型
//的字节数,而数组名则是整个数组的所占空间的字节数,所以上述格式可以求得数组元素的个数


//字面值常量:1,1L,1.5,1.5F,'a',"WTF"等就是字面值常量
}


{ //第30课 一维数组的使用
#include<stdio.h>
int main()
{
int i=0, n=0, yu[16]={0};
printf("欢迎使用二进制转换器\n");
printf("请输入一个0——32767的整数,并按回车结束操作\n");
scanf("%d", &n);
for(i=0;i<16;i++)
{
n/=2;//相当于n=n/2;
yu[i]=n%2;//这句可以与上面的n/=2;互换
}
for(i=15;i>=0;i--) //因为上面的计算结果与想要的结果顺序正好相反所以下面要把结果反过来
{
printf("%d", yu[i]);
if(i%4==0)
printf("\t");
}
return 0;
}
/*yu数组可以保存0-32767的数(2^16-1),共有16位,计算方法为将一个数进行取余运算直到余数小于2为止,
然后把由0和1组成的二进制数组(不是指定义多个同数据类型用的那个数组,而是指如0000000101000011这样的一串数字)倒置
比如计算160时计算的结果为0000010100000000,然后要把它变成0000000010100000的形式*/


//注:十进制数转为其他进制数都可以采用辗转相除法,以上例子即是,属于思路范畴
//二进制转8进制和16进制十分简单,直接可以口算出来,算法:从最低位开始取三位(8进制)或四位(16进制)算十进制,>=10用
//十六进制中的代表10~15的ABCDEF表示,算法原理自导~
}


{ //第31课  二维数组和多维数组 int map[5][4]={{?}};
#include<stdio.h>
int main()
{
int map[5][4]={           //第一个方括号里面的数是行数第二个方括号里面的数是列数
{1,2,3,4},            
{5,6,7,8},
{9,10,11,12},
{13,14,15,16},
{17,18,19,20}, 
};
 
printf("%5d\n", map[0][0]);//输出每一个元素,,,但绝对不建议这样遍历打印整个数组
printf("%5d\n", map[0][1]);
printf("%5d\n", map[0][2]);
printf("%5d\n", map[0][3]);
printf("%5d\n", map[1][0]);
printf("%5d\n", map[1][1]);
printf("%5d\n", map[1][2]);
printf("%5d\n", map[1][3]);
printf("%5d\n", map[2][0]);
printf("%5d\n", map[2][1]);
printf("%5d\n", map[2][2]);
printf("%5d\n", map[2][3]);
printf("%5d\n", map[3][0]);
printf("%5d\n", map[3][1]);
printf("%5d\n", map[3][2]);
printf("%5d\n", map[3][3]);
printf("%5d\n", map[4][0]);
printf("%5d\n", map[4][1]);
printf("%5d\n", map[4][2]);
printf("%5d\n", map[4][3]);
return 0;
}


#include<stdio.h>
int main()
{  
int i, j;
int map[5][4]={
{1,2,3,4},
{5,6,7,8},
{9,10,11,12},
{13,14,15,16},
{17,18,19,20}
};
for(i=0;i<5;i++)
{
for(j=0;j<4;j++)
{
   printf("%d\t", map[i][j]);//用for语句输出所有1——20的数字
}
printf("\n");
}
return 0;
}


#include<stdio.h>
int main()
{                      //用二维数组篇写的迷宫,复制运行看看下效果就算了。。
int i, j;
int map[30][30]={
{1,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1},
{1,0,0,0,0,0,0,0,1,0,1,0,0,1,0,0,0,8,0,0,0,1,0,0,0,1,0,1,0,1},
{1,1,1,1,0,1,1,0,1,0,1,0,1,0,0,0,1,1,1,0,0,0,0,1,0,0,0,0,0,1},
{1,0,0,1,1,0,1,0,0,1,0,0,0,0,1,0,0,1,0,1,0,1,0,1,0,0,1,1,0,1},
{1,0,1,0,8,0,1,1,0,0,8,1,1,1,0,0,1,1,0,1,1,1,0,0,1,0,1,0,8,1},
{1,1,0,0,1,0,0,0,1,1,0,1,0,0,0,1,0,0,0,0,1,0,0,0,1,1,1,1,0,1},
{1,0,0,0,1,1,1,0,1,0,0,1,0,1,0,1,0,1,0,1,0,0,1,1,0,0,0,1,0,1},
{1,1,0,1,0,0,0,1,0,1,0,0,0,1,0,1,0,1,0,1,0,0,0,0,0,1,0,1,0,1},
{1,1,0,1,0,1,0,1,1,0,0,0,1,0,0,0,0,1,0,1,0,1,1,1,1,1,0,1,0,1},
{1,0,0,0,0,1,0,8,0,0,1,0,1,0,1,0,1,1,0,0,0,0,0,0,1,0,0,0,0,1},
{1,0,1,1,1,1,0,0,1,1,1,1,0,1,1,1,1,0,1,0,1,1,1,0,1,0,1,0,1,1},
{1,1,0,0,0,0,0,1,0,0,8,0,1,0,0,0,0,0,0,1,0,0,0,0,1,0,8,0,0,1},
{1,0,0,1,1,0,1,0,1,1,0,1,0,0,1,0,1,1,0,1,1,0,1,0,1,1,1,1,1,1},
{1,1,0,0,1,0,0,0,0,0,0,0,1,1,0,0,1,0,0,1,0,1,1,1,0,1,0,1,0,1},
{1,1,0,1,1,1,1,1,0,0,1,1,1,0,1,0,8,1,1,0,1,0,0,0,0,0,1,0,0,1},
{1,1,0,0,1,0,1,0,1,1,0,0,0,0,0,0,1,0,0,0,1,1,0,1,1,0,1,1,0,1},
{1,0,1,0,1,0,1,0,0,0,0,1,1,0,1,0,0,1,1,1,1,0,1,1,0,0,1,8,0,1},
{1,0,1,0,1,0,0,1,0,1,0,0,1,1,0,1,1,0,0,0,0,0,0,1,0,1,1,1,0,1},
{1,0,0,0,1,0,0,0,0,1,0,1,0,1,0,0,0,0,1,0,1,0,0,1,0,0,0,0,0,1},
{1,0,1,0,0,1,1,1,0,1,0,0,0,1,0,1,1,1,1,0,1,1,1,1,1,1,0,1,1,1},
{1,1,0,0,1,1,0,0,0,0,1,1,0,1,1,0,1,0,0,0,0,0,0,0,0,1,0,0,0,1},
{1,0,1,8,0,0,0,1,1,1,1,0,0,0,0,0,0,0,1,1,1,1,1,0,1,1,1,1,0,1},
{1,0,1,0,1,1,0,1,0,0,0,0,0,1,0,1,1,0,0,0,0,1,0,0,0,0,0,0,0,1},
{1,0,0,0,0,1,0,1,0,1,1,0,0,1,1,0,1,1,1,0,0,0,0,1,1,1,1,1,0,1},
{1,0,1,1,1,1,0,1,0,0,0,1,0,1,0,0,0,0,1,1,0,1,0,1,0,0,0,1,0,1},
{1,0,0,0,0,0,0,1,0,1,0,1,1,1,1,0,1,0,0,1,1,1,0,1,0,1,0,1,1,1},
{1,0,1,1,0,1,0,0,1,0,0,1,0,8,1,0,0,1,0,1,0,1,0,0,0,1,0,0,0,1},
{1,0,1,1,0,1,0,0,1,0,1,1,0,0,1,0,0,1,0,1,0,1,1,1,1,1,0,1,0,1},
{1,0,1,0,0,8,0,0,1,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,1,0,1},
{1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1}
};
for(i=0;i<30;i++)
{
for(j=0;j<30;j++)
{
if(map[i][j]==0)
{
printf("  ");
}
if(map[i][j]==1)
printf("▇");//用这个代表墙
if(map[i][j]==8)
{
printf("+-");
}
}
printf("\n");
}
return 0;
}
}


{ //第32课  指向数组的指针
#include<stdio.h>
int main()

int a[10]={1,2,3,4,5,6,7,8,9,10};
int *str=a;
int i;


for(i=0;i<10;i++)
{
printf("a[%d]=%d\n", i,*(str+i));
}
return 0;
}


#include<stdio.h>
int main()
{
int a[10]={1,2,3,4,5,6,7,8,9,10};//a就是数组的首地址,它是一个常量,不能被赋值的,试试
int *str=a;//str存储的地址就是数组的首地址
printf("a[2]=%d\n", a[2]);
printf("*(str+2)=%d\n", *(str+2));
printf("*(a+2)=%d\n", *(a+2));
printf("str[2]=%d\n", str[2]);
//四种方法都一样
return 0;
}
//可见:str[2]和*(str+2)是等价的,实际编译器也是把str[2]转化成后者来运算


//指针运算:自加,自减,加一个或减一个整型数,指向同一个数组的两个元素的指针的相减,百度指针运算吧。。
}


{ //第33课  保护数组内容,const的使用,  常量指针和指针常量
#include<stdio.h>
int main()
{
const int a[10]={1,2,3,4,5,6,7,8,9,10};
a[8]=100;          //这是语法错误,因为上一句是定义的常量,是无法对其进行动态修改的
printf("%d", a[8]);
return 0;
}


#include<stdio.h>
int main()
{
const int a[10]={1,2,3,4,5,6,7,8,9,10};
int *pn=a;   //可以用指针来进行修改,但是这种方法是不推荐的,编译的时候会出现警告
//这个不知,,在VC上是编译错误的,其他IDE不知行不
pn[8]=100;          
printf("%d", pn[8]);
return 0;
}


#include<stdio.h>
int main()
{
const int a[10]={1,2,3,4,5,6,7,8,9,10};
const int *pn=a;     /*这就定义了一个常量指针(const也可以写在int后面),常量指针的使用要注意,
指针指向的对象不能通过这个指针来修改,可是仍然可以通过原来的声明修改,也就
是说常量指针可以被赋值为变量的地址,之所以叫做常量指针,是限制了通过这个指针修改变量的值*/                         
pn[8]=100;      //语法错误,不能通过常量指针修改地址指向的空间的内容
printf("%d", a[8]);
return 0;
}
//注:关于const修饰指针是个比较麻烦的知识点,根据位置不同的不同,有常指针和上述的常量指针,或者两者的结合


//一般常变量(const变量,包括常指针)和常量指针可用作函数参数:常量指针可避免改变指向的变量;常变量可避免
//在函数体中修改它本身的值,其实用于main函数也是同样道理,另外常量指针用于参数还要注意一些点,先不讲


#include<stdio.h>
int main()
{
const int a[10]={1,2,3,4,5,6,7,8,9,10}, b={0};  //为了更好的理解,我们再定义一个数组名为b的数组
int *const pn=a;        
pn=b; //这也是不合法的,因为上面一句已经定义了指针常量(把指针pn和a的地址进行了绑定),
 //定义了指针常量后不能对指针指向的地址进行修改
pn[8]=100;      
printf("%d", a[8]);
return 0;
}


/*所以指针常量与常量指针的区别是:指针常量如果已经定义且被一个地址进行初始化,
 那么就无法在下面的语句中对地址进行动态修改,而常量指针是让其地址指向的值无
 法进行修改,但是要记住,将常量或非常量数组的地址赋值给常量指针是合法的,但
 只有非常量数组的地址(不一定要是首地址)才可以赋值给普通指针*/
}

{ //第34课  指向多维数组的指针
#include<stdio.h>
int main()
{
int map[4][2]={
{2, 4},   //1
{6, 8},
{1, 3},
{5, 7}
};
int (*p)[2]=map;//方括号2与上面定义二维数组的方括号2对应,p为数组指针,与指针的指针有差别
printf("%d\n", map);
printf("%d\n", &map[0]);
printf("%d\n", &map[0][0]);
return 0;
}
//map就是标号为1的元素的首地址,&map[0]是标号为1的地址,因为map[0]本身也是包含两个元素的数组,
//即&map[0]是该数组的首地址,所以&map[0]=&map[0][0],所以有map=&map[0]=&map[0][0]
//但只是地址相同,用于指针运算并不等价


#include<stdio.h>
int main()
{
        int a[2][4]={
  {11,12,13,14},
  {21,22,23,24}
};
int (*p)[4]=a;
printf("%d", p);//p是指向a(也就是二维数组首元素的地址,即&a[0])的指针  
        return 0;
}
//注:上面程序的p的类型是数组指针。

//关于确定类型有一种方法:根据运算符的优先级别逐步解析
//像上述的定义,先看小括号里面的,确定p为指针,再看方括号,确定为指向四个元素的数组的指针,最
//后再看类型,确定为指向4个整型元素的数组的指针类型,,具体方法解析百度吧~


#include<stdio.h>
int main()
{
   int a[2][4]={
  {11,12,13,14},
  {21,22,23,24}
};
int (*p)[4]=a;
printf("%d", **p);//*p=*a=首元素的值,即a[0]的值,因为a[0]的值又是一个int变量的地址,
 //即11的地址,所以**p就是a[0][0]的值
        return 0;
}


#include<stdio.h>
int main()
{
   int a[2][4]={
  {11,12,13,14},
  {21,22,23,24}
};
int (*p)[4]=a;
printf("%d", **(p+1));/*  p已经知道是等于&a[0],所以首地址加一,就访问到第二行的地址,
    由于第二行的值(就是*(p+1))又是21的地址,所以还需要进行一次
    解引用(*)才能访问到21这个数值*/
        return 0;
}


#include<stdio.h>
int main()
{                                    
        int a[2][4]={
  {11,12,13,14},
  {21,22,23,24}
};
int (*p)[4]=a;
printf("%d", *(*(p+1)+1));/*我们已经知道*(p+1)是21的地址了,现在我们可以想象第二行就是
    一个一维数组,21的地址是这个数组的首地址,那么(*(p+1)+1)
                                就是首地址后面的那个地址即22的地址,当然还需要进行一
                            次解引用(*)才能访问22这个数值*/  
        return 0;
}


#include<stdio.h>
int main()
{
       int a[2][4]={
  {11,12,13,14},
  {21,22,23,24}
};
  int (*p)[4]=a;
  printf("%d", *(*p+1));
       return 0;
}


#include<stdio.h>
int main()
{
int a[4][3]={
{1,2,3},
{4,5,6},
{7,8,9},
{11,12,13}
};
int (*p)[3]=a;
int i, j;
for(i=0;i<4;i++)
{
for(j=0;j<3;j++)
{
printf("%d\t", *(*(p+i)+j));//里面的括号是行,外面的是列这句相当于 printf("%d\t", a[i][j]);
}
printf("\n");
}
return 0;
}
}


{ //第35课函数的自定义和调用     void myfuction(void);
#include<stdio.h>
void myfuction(void);//声明一个函数
int main()
{
printf("%d\n", 123);
myfuction();//函数调用的顺序在第一个printf后面
printf("%d\n", 789);
return 0;
}
void myfuction(void)  //定义函数,这部分也可以写在int main()上面,且函数头也会起到声明函数的作用
 //即,把函数定义放到main之上,函数声明可以去掉
{
//函数的内容
printf("%d\n", 456);
}
//注:写函数的函数头时,若不需要传参,可以直接在函数名后接一个小括号,也可以在小括号加void
}


{ //第36课 定义带参数的函数
#include<stdio.h>
void mychar(int);
int main()
{
int i=0;
for(i=1;i<10;i++)
{
mychar(i);
//i初始化为1,调用一次mychar,执行mychar里的函数体,然后i加1,判断2小于10成立,再次调用mychar
printf("\n");
}
return 0;
}
void mychar(int a)
{
int i=0;
for(i=0;i<a;i++)
/*每次mychar被main函数调用,都会顺序执行这里的语句,所以main函数每调用一次mychar,
 这里的i都会初始化为0,然后i++,判断i是不是小于a。main每次调用mychar,mychar里的
 i都是从0开始自加*/
{
printf("*");
}
}

#include<stdio.h>
void mychar(char);
int main()
{
char a;
a=getchar();
mychar(a);
return 0;
}
void mychar(char ch)
{
if(ch>='a' && ch<='z')
putchar(ch-('a'-'A'));
else 
{
putchar(ch);
}
}
}


{ //第37课 形式参量和局部参量
#include<stdio.h>
void add(int,int);
int main()

int a=1,b=2;//这个赋值与下面自定义函数的a,b的值无关,因为作用域不同
add(5, 5);//实际参数是两个5,当main函数调用自定义函数的时候,把两个5的值分别赋值给a和b
return 0;
}
void add(int a, int b)//形式参数是a和b,被赋值后的a和b的值分别为5和5,且这些值在当前函数可用
{
printf("%d+%d=%d",a,b,a+b);
}


#include<stdio.h>
int a, b;//定义全局变量,全局变量可用在整个程序中使用,但多文件程序需要注意一些点,再讲
void add(int, int);//与形式参量的类型对应
int main()
{
a=1;//a=1是对全局变量进行修改,如果定义成int a=1;那就变成了main函数中的局部变量,局部变量
//只可以在该函数中使用,屏蔽了全局变量,与其他函数中a的值没有关系,因为作用域不同
b=2;//这个也是
add(5, 5);//定义两个实际参量他们赋值给形式参量,且一一对应
return 0;
}
void add(int x, int y)//xy的值都是被赋值为5但是下面的函数没有使用
{
printf("%d+%d=%d", a, b, a+b);//这里使用的是全局变量的a和b,由于a和b在main函数中被赋值,所以a=1,b=2
}
    //这个程序的自定义函数在printf函数中使用了全局变量,如果再定义一个变量名相同的局部变量(int a=12)在此函
//数中那么a的值就会覆盖掉全局变量,但是a=12只是给这个函数使用并不会改变其他函数的值,因为它的作用域是这个函数内。
}
//关于变量有两个概念需要清楚:作用域和生命期,后面会具体解析


{ //第38课 返回值和返回类型
#include<stdio.h>
int add(int a, int b);
int main()
{  
printf("%d", add(2,4));//将返回给自定义函数的值输出
return 0;
}
int add(int a, int b)//int标明会在函数执行后返回一个int类型的值
{
return a+b;//返回一个a与b的和,因为返回的是整数,所以要把返回类型设为int,他们的和会返回给自定义的函数
}


#include<stdio.h>
#include<malloc.h>
#include<string.h>
char* getstr();  //声明一个返回字符串的函数  getstr                                   
int main()
{
        char *p=NULL;  //定义一个字符型空指针P
        p = getstr();  //将getstr的返回值(字符串str)赋值给p
        puts(p);       //输出字符串P
        free(p);       //释放p指向的内存空间
        return 0;
}
char *getstr()  //getstr函数实现
{
        char *str=NULL;   //定义一个字符型空指针str
        str =(char *)malloc(sizeof(char)*512);   //使用malloc函数声明一块512字节的字符型内存空间并赋值给p,
//malloc分配成功时会返回一个指向被分配内存的指针
        memset(str, 0, sizeof(char)*512); //将str指向的内存空间清0,防止出现不可预料的值
            /*void *memset(void *s, int ch, size_t n);
              函数解释:将s中前n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。
              memset作用:在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法*/


        return gets(str);   //返回str的地址给主函数,赋值给主函数中的p,输入的字符串存在str的空间中
}
}
//指针类型的写法,当定义一个指针时,*接在变量名前如:int *p;当在声明函数或强制类型转换时*直接连在类型名,如:int*
//不过只是建议,,


{ //第39课 指针做函数的参数
#include<stdio.h>
void test(int*);//定义一个整形指针参数
int main()
{
int a=50;
test(&a);//我们需要对地址指向的值也就是50进行修改,所以我们通过传地址进行数据修改是最简单的
printf("%d", a);
return 0;
}
void test(int *n)//将a的地址传给指针n
{
*n=10;//这里进行了通过给地址赋值的方法,改变数据的值(把50改为10),*n是指针n指向地址的值原来是50,现在是10
}
//传地址,通过指向数据的指针对数据进行修改和访问,但是数据只有一份,这些操作都是在一份数据上进行
//传值,相当于将数据复制一份出来,后面的修改和访问,都是在复制出来的那份数据上进行


//指针作为函数参数有很多好处:值传递时节省时间;可以通过指针对一个或多个原变量进行操作,于是有返回多个值的效果,
//具体分析以后讲


#include<stdio.h>
#include<string.h>
#include<malloc.h>
void getmemory(char **p)//定义指针的指针,即存储指针地址的指针类型为形参
{
*p=(char *)malloc(100);//这里*p实际上为存储char数据的空间的首地址
strcpy(*p, "hello word");//将字符串的首地址开始的存储的内容赋给以p指向的地址为首地址的一段空间,'\0'会作为结束标志
//另注:提过了,字符串字面值常量类型为const char*
}
int main()
{
char *str=NULL;
getmemory(&str);//需要记住一点,传什么就要在实参里面加上要传东西的地址,&为取地址运算符
//比如传变量a,实际参量就是&a。如果是str就是&str
puts(str);//输出str中地址的内容
free(str);//清空存储空间
str=NULL; //这十分重要!!
return 0;
}
//注:当进行动态内存分配操作时,当释放内存后,指针便成为了野指针,野指针十分危险!所以要把它赋为NULL!百度吧,,有机会再讲
}


{ //第40课  数组做函数的参数
#include<stdio.h>
void add(int n[]);//一定要输入形参类型,方括号里面什么数字都行,编译器只会看形参的类型,个数无所谓的
 //另外一维数组可以写成指针类型。但高维数组除了最高维数组的维数可以不写,低维的维数
 //一定要写,这样编译器才能根据下标算得地址,普通高维数组的定义同样如此,具体分析百度
int main()
{
int x[5]={1,2,3,4,5};
add(x);//只需要输入实参x(数组首地址),这样就传了地址
  //(如果传变量才需要取地址:&变量名)并可以进行修改,这里将x传给n
return 0;
}
void add(int n[])
{
int i;
for(i=0;i<5;i++)
{
printf("%d", n[i]);
}
}
//注:数组做参数因为是传递了地址,所以对数组的操作实际是对原数组的操作
//    但必须注意:一维数组的数组元素做参数不传地址,所以与原来数组无关


#include<stdio.h>
void test(int t[]);
int main()

int i;//定义main函数的局部变量,这个变量不会影响函数test的变量
int n[5]={1,2,3,4,5};//如果我们想保护数组不被修改,可以在int 前加const,
//也可以在函数声明的函数列表里写const,并在函数定义里面也要写
test(n);//用传地址对数组进行修改
for(i=0;i<5;i++)//然后输出
{
printf("%d", n[i]);
}
return 0;
}
void test(int t[])
{
int i;
for(i=0;i<5;i++)
{
t[i]=5+i;//如果加了const这句就会出错
}
}

#include<stdio.h>
void test(int);
int main()

   int n[5]={1,2,3,4,5};
test(n[0]);//实际上实参就相当是变量,传的是值,n[0]=1
        return 0;
}
void test(int a)
{
printf("%d", a);
}//这种方法是不能对原来数组元素进行修改的,跟普通变量一样道理


#include<stdio.h>
void test(int *);
int main()

   int n[5]={1,2,3,4,5};
test(&n[0]);//由于n[0]可以看做一个变量,所以它做实参的时候要取其地址
printf("%d", n[0]);
return 0;
}
void test(int *a)//地址不能直接赋值给变量,要赋值给一个指针
{
*a=12;//*a不是指针a,而是指针a指向的变量
}
//其实,若:int a,*p=&a;那么在后面的操作*p就等价于a变量
}


{ //第41课 递归
#include<stdio.h>
int main()

int s,i;
for(i=1,s=1;i<=4;i++)
{
s=s*i;
}
printf("%d", s);
return 0;
}
//1*1=1;  1*2=2;  2*3=6;  6*4=24;其实就是1*2*3*4=24  


/*如果我们要用递归来解,那么我们如果算4的递归(4!)那么就要算出3的递归(3!)然后乘他本身4,
 但是算3的递归又要算2的递归......如下所示
4!=3!*4
3!=2!*3
2!=1!*2
1!=1      
其实4!=(1*2*3)*4,而 (1*2*3)就是3!,只是运用了乘法结合率*/


#include<stdio.h>
long fact(int);
int main()

printf("%d", fact(2));//4太繁杂了,我们举实参为2这个栗子
return 0;
}
long fact(int n)//那么n=2
{
long f;//f是某个数递归的结果
if(n>1)//因为n=2大于1所以执行这个if语句
{
f=fact(n-1)*n;//n-1=1;再通过调用函数本身算得 fact(n-1)的值为1.,括号外面的n还是2。
 //由于2!=1*2所以就算出来了,如果是4的递归那么((((1)*2)*3)*4)           
}
else
{
f=1;//这里的f略有不同这里的f是 fact(1)的返回值
}
return f;//这里的返回f的操作是分情况而定的,第一次返回是当n的值为1并完成了else语句后,
//将f=1返回给if语句里面的调用语句fact,后面的返回是返回if语句中的f
}//其他的只是运算过程多了,本质是一样的,还有如果计算的是2!那么要返回两次f的值,
//一次是返回给if语句中的调用函数,一个是把if语句里面的计算结果返回给main函数
//通常说我们通过return语句返回给调用函数(fact(n-1))而返回后的调用函数可用
//做普通的变量来进行运算
}
//注:关于递归,其实他是一种编程思路,它的优点是代码量少,思路巧妙。但通常内存开销大,运行时间慢
//  所以递归的写法选择要权衡再三。


//    与递归思路相对应的数据结构是栈。栈可以看成一个容器,特点是:先进后出,后进先出。


//    递归即:不满足条件,继续运行含调用本函数的语段,即不断深入底层,最后
//    满足终止条件即停止继续调用函数,开始返回上一次函数调用,直到第一层算得最终值。


//  递归函数必须有终止条件,通常的写法为if...else,另外宁战传的“递归解析”分析得挺好,值得参考


{ //第42课 字符串I/O(input/output)
#include<stdio.h>
int main()
{  
char a[]="www.zixue7.com";//a保留字符串的首地址
int i;
for(i=0;a[i]!='\0';i++);//循环体里面没有任何东西,所以for后面加分号,或者说循环体是一个空语句
printf("%d", i);//i=0的时候对应了字符w,直到遇到\0为止,但是遇到的时候虽然不会执行循环体
//(假设这里有循环)但是还是会执行一次i++
return 0;
}
//注:对for语句的三个表达式的执行顺序要明了:初始化表达式在for语句开始时运行,并且只运行一次,然后
//判断条件,条件满足才执行循环体,不满足就跳出循环了,循环体执行一次后调用第三个表达式进行调整,再
//又一次判断条件,以此顺序直到for语句执行完


#include<stdio.h>
int main()

char *str="www.zixue7.com";  //我们可以通过将指针指向字符串首地址的方法对字符串进行操作
int i;
for(i=0;*(str+i)!='\0';i++);//这里的str[i]可以改成*(str+i)
printf("%d", i);
return 0;
}

#include<stdio.h>
int main()

char str[300]="";  //如果字符指针用在双引号里面有字符的情况方括号里面可以为空,
  //程序自己会对字符串的长度分配合理的空间,双引号里面为空就要分配空间
scanf("%s", str);
int i;
for(i=0;*(str+i)!='\0';i++);
printf("%d", i);
return 0;
}


#include<stdio.h>
#include<malloc.h>
int main()

char *str=NULL;//用NULL初始化指针
str=(char *)malloc(100);//我们需要用malloc分配存储空间
scanf("%s", str);
int i;
for(i=0;*(str+i)!='\0';i++);
printf("%d", i);
free(str);//释放空间
str=NULL; //避免野指针!
return 0;
}

#include<stdio.h>
#include<malloc.h>
int main()
{   int i;
char *str=NULL;
str=(char *)malloc(10000);
scanf("%s", str);
for(i=0;*(str+i)!='\0';i++)//*(str+i)可以换成str[i]
{
if(*(str+i)>='A' && *(str+i)<='Z')//如果想判断输入的是不是大写字母,需要这种操作。并使大写字母通过if语句变成小写的
{
*(str+i)=*(str+i)+32;//这里的32其实就是ascii码中的'a'-'A'的差
}
}
printf("%s", str);
free(str);
str=NULL;
return 0;
}
}


{ //第43课 字符串的输入输出 gets()   puts()     scanf()与gets()的区别   fgets()  fputs()


//gets()的用法是读取输入的一串字符,如果读取成功,那么返回与参数buffer(缓冲区)相同的指针


//fgets()
#include<stdio.h>
int main()
{
char a[100]={0};//这里{0}可以换成双引号
gets(a);//读取一串字符将首地址返回给地址a(所以之后a就是字符串的首地址)
printf("%s", a);//将字符串输出
return 0;
}


#include<stdio.h>
int main()
{  
char a[100]="";
fgets(a, 100, stdin);
//fgets(获取到字符串放哪里,指定缓冲区的大小,从哪里获取);    用法类似于gets()
fputs(a, stdout);//这里不用输入空间的大小,stdout(标准输出缓存)即将缓冲区的内容输出,
//stdin(标准输入缓存)将输入的内容放进缓冲区   用法类似于puts()
return 0;
}


#include<stdio.h>
int main()
{  
char a[100]="";
scanf("%s", a);//scanf在遇到Enter或空格时结束,如果输入qwe qwe,那么只读取空格前面的字符qwe。
  //gets()则只在遇到Enter时结束,
puts(a);       //可以读取空格输入qwe qwe,则读取整串字符
return 0;
}
}


{ //第44课 处理字符串的库函数
#include<stdio.h>
#include<string.h>
int main()
{  
char a[100]="";
gets(a);
printf("你输入了%d个字符组成的字符串:", strlen(a)); //函数strlen()用来检查字符串的字符个数
//但'\0'不会包含在内
puts(a);
return 0;
}

#include<stdio.h>
#include<string.h>
int main()
{  
char a[100]="";
char b[150]="";
gets(a);
gets(b);
strcat(a, b);//输入a, b两个参量,要是地址
printf("%s", a);//函数的实际参数应该是连接字符串函数(strcat()这个)中逗号前面的那个地址
return 0;
}

#include<stdio.h>
#include<string.h>
int main()
{  
    
char a[120]="";
char b[120]="";
gets(a);                    //strcmp()可以判断字符串两个字符在ascii码中的十进制和的大小
//如同字典般,前面字母相同则继续比较下一个字母,返回的值实际
//为第一对不同字母的对应的ASCII码值的差
gets(b);
if(strcmp(a, b)==0)   //a==b的时候返回0
{
printf("两个字符串相等");
}else if(strcmp(a, b)<0)  //a<b时返回负数
{
printf("第二个字符串字符的ascii码比较大");
}else if(strcmp(a, b)>0)//a>b时返回正数
{
printf("第二个字符串字符的ascii码比较小");
}
return 0;
}


#include<stdio.h>
#include<string.h>
int main()
{      
char a[120]="";
char b[120]="";
gets(a);                   
gets(b);
if(strncmp(a, b, 3)==0)   //格式为,strncmp(字符串1的首地址, 字符串2的首地址, 比较什么范围的字符串长度)
{                         //这种函数只会比较一定字符数量中的字符的ascii码所以要规定比较的范围
printf("两个字符串相等");
}else {
printf("两个字符串不相等");
}
return 0;
}


#include<stdio.h>
#include<string.h>
#include<malloc.h>
int main()
{  
char *str=NULL;
str=(char *)malloc(1000);
strncpy(str,"wwwadadsda", 3);//strncpy(拷贝字符串到什么地址下,字符串的首地址, 拷贝字符串的前多少个字符);
printf("%s", str);
return 0;
}
//字符串操作库函数还是比较容易实现的,可以试着自定义函数实现类似效果。


//后面的几个程序未经过检阅,,因为涉及到的库函数审阅者不清楚。。有兴趣百度了解下函数功能用法,如有错请指出
#include<stdio.h>
#include<string.h>
int main()
{  
char *p;
  char a[100]="";
char b[100]="";
gets(a);         //strstr函数将逗号前面的字符串定义成被查找目标,逗号后面的定义为要查找目标,以a,b为例若b是a的子串,
gets(b);         //则先确定b在a的第一次出现的位置,并返回此a在b首位置的地址给函数。;如果b不是a的子串,则返回NULL。
p=strstr(a, b);  //如果字符串a为www.zixue7.com,字符串b为z,由于z是字符串a的子串(因为字符串b的首地址值在字符串a中出现过)那么将z在字符串a中的地址传给函数
printf("%s", p); //则指针p中保存了字符串a中字母z的地址,输出的是这个地址的值及这个地址后面的值
return 0;        //如果字符串b中的首字母没有在字符串a中出现过,那么返回给函数的指针为空         
}


#include<stdio.h>
#include<string.h>
int main()
{  
char *p;
  char a[100]="";
char b[100]="";
gets(a);       
gets(b);       
if(p=strstr(a, b))
{
printf("%d", (int)(p-a+1));//可以通过这种方式打印查找字符串的字符是第几个,p是被查找的字符串首字母的地址,a是字符串a的首地址
}                       //字符串a如果是www.zixue7.com,那么如果输入w,那么p的地址就=字符串a的首地址,那么p-a=0,由于程序结果的需要,我们需要加1
return 0;                 
}


#include<stdio.h>
#include<string.h>
int main()
{  
   
  char a[100]="";
char b[100]="";
gets(a);       
gets(b);       
sprintf(a, "%s%d%s", a, strlen(a), b);
puts(a);                //sprintf(将几个数据的地址合并为哪个地址, "几个数据的类型", 数据1, 数据2······);
return 0;                 
}
}


{ //第45课 什么是文件
//一个文件通常就是磁盘上一段命名的存储区
    //C语言将文件看成是连续的字节序列,其中每一个字节都可以单独读取
//C程序为你打开3个文件,分别为标准输入,标准输出,标准错误输出。默认的标准输入是系统的输入设备,一般为键盘,输出一般是显示器
}


{ //第46课 用fopen()打开文件,用fclose()关闭文件
//fopen()可以用于打开一个文件,如果成功就返回此文件的指针,如果失败就返回空指针,并将错误信息存在errno中
//fclose()课用于关闭已经打开的文件括号里面为文件的指针

#include<stdio.h>


int main()
{  
   FILE *fp=NULL;      //这里的FILE必须是大写,其实可以将其看做一个类型来记忆,*fp是结构体指针
   fp=fopen("c:\\zixue7.txt", "w");//fopen("文件的打开路径(如果文件没有就新建一个文件)", "模式字符串");
   if(fp==NULL)
   {
  printf("读取文件失败");
  return 0;//这里的return 0;是为了在文件读取失败的时候将程序结束掉,不然的话后面对文件的操作将是无意义的
   }
   fclose(fp);//关闭文件
return 0;                 
}
/*常用模式字符串:
        r:只读方式,文件必须存在


        w:只写方式,若文件存在,则原有内容会被清除;若文件不存在,则会建立文件


        a:追加方式打开只写文件,只允许进行写操作,若文件存在,则添加的内容放在文件末尾;若不存在,则建立文件


        +:可读可写


        b:以二进制方式打开文件


        t:以文本方式打开文件(默认方式下以文本方式打开文件)


   下面是常见的组合:


        r:      以只读的方式打开文件,只允许读,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部


        r+:    以可读可写的方式打开文件,允许读写,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部


        rb+:  以可读可写、二进制方式打开文件,允许读写,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部       


        rt+:  以可读可写、文本方式打开文件,允许读写,此文件必须存在,否则返回NULL,打开成功后返回文件指针,位置指针指向文件头部       


        w:    以只写的方式打开文件,只允许写,若文件存在,文件中原有内容会被清除;若文件不存在,则创建文件,打开成功后返回文件指针,位置指针指向文件头部


        w+:  以读写的方式打开文件,允许读写,若文件存在,文件中原有内容会被清除;若文件不存在,则创建文件,打开成功后返回文件指针,位置指针指向文件头部


        a:     以追加、只写的方式打开文件,只允许写。若文件存在,则追加的内容添加在文件末尾,若文件不存在,则创建文件。打开成功后返回文件指针,位置指针指向文件头部(注意很多书上或资料上讲述追加方式打开成功后位置指针指向文件末尾是错误的)


        a+:   以追加、可读写的方式打开文件,允许读写。若进行读操作,则从头开始读;若进行写操作,则将内容添加在末尾。若文件不存在,则创建文件。打开成功后返回文件指针,位置指针指向文件头部。


*/
}


{ //第47课  getc()和putc()读写文件


#include<stdio.h>


int main()
{  
   char ch;
   FILE *fp=NULL;     
   fp=fopen("c:\\zixue7.txt", "wt");
   if(fp==NULL)
   {
  printf("读取文件失败");
  return 0;
   }
while(ch=getchar())
{
   putc(ch, fp);//putc(一个字符或char型变量, 字符指向的文件指针);

fclose(fp);
return 0;                 
}


#include<stdio.h>
int main()
{  
   char ch;
   FILE *fp=NULL;     
   fp=fopen("c:\\zixue7.txt", "r");//用读取功能的模式字符串
   if(fp==NULL)
   {
  printf("读取文件失败");
  return 0;
   } 
ch=getc(fp);
while(ch!=EOF)//EOF=end of file 文件结束
{
    putchar(ch);//这里不用putc是因为我们需要输出的是已经读取的字符,而putc是把字符输入到文件里
ch=getc(fp);//gets的用法与puts差不多
}
fclose(fp);
return 0;                 
}
}


{ //第48课 文件结尾feof()函数
    //feof()函数如果文件结束则返回非零值,否则返回0。文件结束符只能被clearern()清除
#include<stdio.h>
int main()
{  
    char ch;
FILE *fp=NULL;
fp=fopen("c:\\zixue7.txt", "r");
    if(fp==NULL)
{
printf("文件读取失败");
return 0;
}
    ch=getc(fp);
    while(feof(fp)==0)// while(feof(fp)==0)这句实际相当于while(ch!=EOF)因为字符不等于EOF的时候返回值全部是0,当返回值是0的时候,ch=EOF
{
putchar(ch);
ch=getc(fp);
}
fclose(fp);
return 0;
}


#include<stdio.h>
char mygetc(char *);
int i=0;//此变量只可以定义为全局变量,不可以定义在函数mygetc中,因为每次调用mygetc的时候i的值都初始化为0,
int main()//也不可定义在main函数里,因为会在mygetc里面缺少对变量i的定义。对全局变量的修改是永远存在的(除非你再次进行修改,那么新的值会覆盖旧的值)
{  
   char *fp;
   char a[100]="www.zixue7.com";
   fp=a;            //其实以上三句相当于char *fp="www.zixue7.com";就是把字符串的首地址传给指针
   char ch=0;
   ch=mygetc(fp);
   while(ch!='\0')
   {
  putchar(ch);
  ch=mygetc(fp);
   }
   
return 0;
}
char mygetc(char *c)//c中保留了字符串的首地址即字符w的地址
{
return *(c+i++);//假设现在i=0那么首地址加一等于字符串中,第二个字符(也是w)的地址,然后通过*取值
}                //i++对全局变量的修改是永远存在的
}


{ //第49课 fgets()与fputs()
//fgets会读取一行字符串,包括行尾的'\n',并且会自动在字符串末尾添加'\0'
//fputs(字符型指针(或字符串常量,字符串数组首地址),文件指针(输入的东西存入哪里));
#include<stdio.h>


int main() 
{    
    FILE *fp=NULL;
    char str[100]="";
fp=fopen("c:\\zixue7.txt", "r");
    if(fp==NULL)
{
printf("文件读取失败");
return 0;
}
while(feof(fp)==0)
{
strnset(str, '\0', 100); //strnset将str字符串中的前100个字符都设为指定字符'\0',这句去掉也行,因为下面的fgets会自动添加'\0'
printf("%s", fgets(str, 100, fp));//从文件中读取数据到100个字节的缓冲区中,并存入str中
}
return 0;
}


#include<stdio.h>


int main() 
{    
    FILE *fp=NULL;
    char str[100]="";
fp=fopen("c:\\zixue7.txt", "r");
    if(fp==NULL)
{
printf("文件读取失败");
return 0;
}
printf("%s", fgets(str, 100, fp));//假设文件第一行是www.zixue7.com,第二行是www那么只会读取第一行,即www.zixue7.com

return 0;
}


#include<stdio.h>
int main() 

FILE *fp=NULL;
fp=fopen("c:\\zixue7.txt", "w");
    if(fp==NULL)
{
printf("文件读取失败");
return 0;
}


fputs("www.zixue7.com", fp);
return 0;
}


}


{ //第50课 文件内部指针
#include<stdio.h>
char mygetc(char *);//相当于putc函数
int i=0;//相当于文件内部指针
int main()
{  
char *fp="www.zixue7.com";//fp相当于文件指针
char ch;
ch=mygetc(fp);
while(ch!='\0')
{
putchar(ch);
ch=mygetc(fp);
}


return 0;
}
char mygetc(char *c)
{
return *(c+i++);
}
//文件内部指针是可以文件操作函数自己修改的,而文件指针只是指向了文件的地址而无法操控文件的内容
//比如说putc在输入一个字符的时候,指针指向下一个字符所在的地址
}


{ //第51课  rewind函数
#include<stdio.h>
int main()
{  
FILE *fp=NULL;
fp=fopen("./zixue8.txt", "r");
if(fp==NULL)//这句括号中的语句相当于!fp,fp是文件,!fp是文件没被打开
{
printf("打开文件出错");
return 0;
}
while(feof(fp)==0)//这句括号中的语句相当于!feof(fp),feof函数可以判断文件是否结束,即feof(fp),而!可以理解成否的意思,即文件没结束
{
putchar(getc(fp));//读取完文件的内容的时候,也就是while语句被打破的时候。由于文件操作函数getc自动把文件内部指针移到了文件末尾,所以下面的while语句
}                     //根本一开始就没满足条件。还有文件操作函数是在读取或输入一个文字、数字、字符的时候,文件内部指针自动指向后一个地址
rewind(fp);  //此函数将文件内部指针指向到文件开始处,这样一来就可以满足下面的while语句的条件了




fclose(fp);
return 0;
}
/*假设文件里是www.zixue7.com两行,那么第一个while语句执行完后会输出他。但是由于rewind函数与第二个while语句的共同作用,
              www.baidu.com
 
输出结果是这样 www.zixue7.com                     因为输出第一次两行网址的时候,由于文件里面在你输入第一行以后,你键入了Enter键让第二个地址在第二行输出。
                www.baidu.comwww.zixue7.com      所以输出了两行网址,运行到第二个while的时候由于你在文件中输入www.baidu.com的时候,后面没加Enter
                www.baidu.com               所以这次while里面通过getchar输出的语句是接着www.baidu.com后面的。也就是说getc可以读取包括Enter
                                  这类的字符
*/



}


{ //第52课 随机存取   fseek()和ftell()函数
#include<stdio.h>


int main()
{  
FILE *fp=NULL;
fp=fopen("./zixue8.txt", "r");
if(!fp)
{
printf("文件读取失败");
return 0;
}
fseek(fp, -29, SEEK_END);//fseek(指向文件的指针, 把文件内部指针向前还是向后移动, 把文件内部指针指向文件的什么的方);
while(feof(fp)==0)       //如果第一个逗号后面是负数就代表把文件内部指针向前移动,反之就向后移动,为了减少计算的时间,我们通常把这个值的绝对值定义的大些
{                        //比如-29就定义成-40,30就定义成40.总之不会影响结果,具体情况还要看文件的内容
putchar(getc(fp));
}
return 0;
}

#include<stdio.h>


int main()
{  
putchar(-1);
putchar(65);
return 0;
}

#include<stdio.h>


int main()
{  
FILE *fp=NULL;
fp=fopen("./zixue8.txt", "r");
if(!fp)
{
printf("文件读取失败");
return 0;
}
getc(fp);//每获取一次文件内部指针向后移动一个单位
getc(fp);
getc(fp);
getc(fp);//一共移动了4个单位(从首位置开始数)
fseek(fp, -4, SEEK_CUR);//SEEK_CUR是从文件内部指针指向的当前位置
printf("文件内部指针当前位置:%d\n", ftell(fp));//ftell可以读取文件内部指针的当前位置
while(feof(fp)==0)
{
putchar(getc(fp));
}
return 0;
}

#include<stdio.h>


int main(int argc, char **argv)//argc参数的个数,**argv是参数的值
{  
int i;
FILE *fp=NULL;
fp=fopen(argv[1], "r");
if(!fp)
{
printf("文件读取失败");
return 0;
}
printf("输入参数的个数为:%d\n", argc);
for(i=0;i<argc;i++)
{
printf("参数%d的值:%s\n", i+1, argv[i]);
}


return 0;
}

#include<stdio.h>


int main(int argc, char **argv)//argc参数的个数,**argv是参数的值
{  
FILE *fp=NULL;
int i;
long len=0;//因为文件可能很大,所以定义一个long形变量
if(argc!=2)return 0;//如果输入的参数等于2就结束程序,因为我们需要在运行程序的时候输入a +文件名。这里a和文件名就是两个参数
fp=fopen(argv[1], "r");//打开参数值,即文件。方括号里面的1其实就跟数组的下标一样,其实就是第二个参数
if(!fp)
{
printf("文件读取失败");
return 0;
}
fseek(fp, 0, SEEK_END);//把文件内部指针指向文件末尾
len=ftell(fp);//读取文件的字节,即文件内部指针指向的地方
printf("当前文件大小为:%d", len);
return 0;
}


}


{ //第53课 fgetpos()和fsetpos
//fgetpos是一个函数名,功 能是依据当前文件的句柄(内部文件指针当前所在位置),获取当前访问指针位置信息。运行成功返回0,否则返回非0
#include<stdio.h>


int main(int argc, char **argv)//argc参数的个数,**argv是参数的值
{
if(argc!=2)return 0;
fpos_t len;
FILE *fp=NULL;
fp=fopen(argv[1], "r");
if(!fp)
{
printf("文件读取失败");
return 0;
}
fseek(fp, 0, SEEK_END);
if(fgetpos(fp, &len)==0)//fgetpos(指向文件的指针, fpos_t类型的变量地址);这个函数可以将文件指针指向文件的大小的值赋予逗号后面的变量地址名下
{
printf("文件大小为%d字节", len);//变量len已经存储了文件的大小
}
return 0;
}
/*从文件编码的方式来看,文件可分为ASCII码文件和二进制码文件两种。


  ASCII文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII码。例如,数5678的存储形式为:
ASC码:  00110101 00110110 00110111 00111000
     ↓     ↓    ↓    ↓
十进制码:53   54   55    56 共占用4个字节。ASCII码文件可在屏幕上按字符显示, 例如源程序文件就是ASCII文件,用DOS命令TYPE可显示文件的内容。 
由于是按字符显示,因此能读懂文件内容。二进制文件是按二进制的编码方式来存放文件的。 例如, 数5678的存储形式为: 00010110 00101110只占二个字节。
二进制文件虽然也可在屏幕上显示, 但其内容无法读懂。C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。
 输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。 因此也把这种文件称作“流式文件”。
一个文件可以以文本模式或二进制模式打开,这两种的区别是:在文本模式中回车被当成一个字符'/n',而二进制模式认为它是两个字符0x0D,0x0A;
如果在文件中读到0x1B,文本模式会认为这是文件结束符,也就是二进制模型不会对文件进行处理,而文本方式会按一定的方式对数据作相应的转换。
*/

}


{ //第54课 freed()和fwhite()函数
#include<stdio.h>


int main(int argc, char **argv)
{
if(argc!=2)return 0;
char ch[500]="www.zixue7.com";
fpos_t len;
FILE *fp=NULL;
fp=fopen(argv[1], "w+");
if(!fp)
{
printf("文件读取失败");
return 0;
}
fseek(fp, 0, SEEK_END);
if(fgetpos(fp, &len)==0)
{
printf("文件大小为%d字节\n", len);
}
rewind(fp);//由于已经被fseek函数把文件内部指针指向了文件末尾,所以我们需要将文件指针重新指向文件开头
fwrite(ch, 14, 1, fp);//fwrite(存储字符串的常量, 写入的长度, 写入多少次, 写入文件指针指向的文件);
fread(ch, 20, 5, fp);//fread(读取的字符串存入哪里, 读取的大小(多少字节,如果超过文件总字节不会影响结果), 读取多少次(不是重复读取), 读取的文件指针);
printf("%s", ch);    
return 0;
}

#include<stdio.h>
#include<malloc.h>
int main(int argc, char **argv)
{
if(argc!=3)
{
printf("输入的命令不正确");
return 0;
}
fpos_t len=0;//fpos_t类型可以使得文件的大小读取的足够精准
char *ch=NULL;    //为了用malloc函数分配一个存储空间  
FILE *fp=NULL;//定义一个文件指针
fp=fopen(argv[1], "r");
if(!fp)
{
printf("文件读取失败%s", argv[1]);
return 0;
}
FILE *fd=NULL;//因为我们需要将一个文件的内容复制到另外一个文件中,所以需要两个文件指针
fd=fopen(argv[2], "a");
if(!fd)
{
printf("文件读取失败%s", argv[2]);
return 0;
}
fseek(fp, 0, SEEK_END);//将文件内部指针指向文件末尾,这是为了读取文件大小
if (fgetpos(fp, &len)==0)//通过fgetpos函数读取文件内部指针所指向的位置,即文件大小
ch=(char*)malloc(sizeof(char)*len); //尽量定义的大些,但是为了避免存储区域的浪费,我们在前面就读取了文件大小并赋值给len这样就可以根据文件的大小,合理分配空间
printf("文件大小是:%d字节\n", len);
rewind(fp);//因为前面读取文件大小的时候把文件内部指针指向了文件末尾,所以我们需要把文件内部指针重新指向文件开头,这样才能保证后面的读取顺利进行
fread(ch, len, 1, fp);//读取指针fp指向的文件,并将值传给ch
fwrite(ch, len, 1, fd);//将ch里面的值写入指针fd指向的文件
printf("已经把文件%s成功复制到%s末尾", argv[1], argv[2]);//用了存储空间千万不要忘记释放
free(ch);
return 0;
}




}


{ //第55课 结构体的定义和初始化
//结构体(struct)是由一系列具有相同类型或不同类型的数据构成的数据集合,叫做结构。在C语言中,结构体(struct)指的是一种数据结构,是C语言中聚合数据类型
#include<stdio.h>
int main()
{                         
struct PERSON          //结构体PERSON中有三个变量,并且有两个不同类型,这样写不能少掉结构体名
{
int age;
int tall;
char name[10];
};//不要忘记分号,这与二维数组的定义方式相似,只是PERSON后面没有等号
struct PERSON p1={12, 150, "tom"};//struct PERSON可以看成一个变量的类型,变量后面用数组的格式定义为结构体中的变量赋值
printf("%d %d %s", p1.age, p1.tall, p1.name);//结构体变量名加小数点加结构体中的变量名
return 0;
}
//注:上述的结构体类型名为struct PERSON,定义变量时注意写完整。当然有一些技巧省略struct直接让PERSON代表该类型,后面讲
//上述PERSON类型的定义由于是在main函数内,所以它的作用域也就只限于main函数内,考虑在main函数后自定义fun函数,函数内定义一个PERSON变量

#include<stdio.h>
#include<string.h>
int main()
{                         
struct PERSON         
{
int age;
int tall;
char name[15];//要定义为一个数组
}p1={12, 150, "tom"};//这种方法为结构体里面的定义变量的同时初始化,但是定义联合体的时候不要用这种方式
printf("%d %d %s", p1.age, p1.tall, p1.name);
return 0;
}

#include<stdio.h>
#include<string.h>
int main()
{                         
struct       
{
int age;
int tall;
char name[15];
}p1; //或者这样,但是下面的赋值语句只能在main函数里面
p1.age = 12;
p1.tall = 150;
strcpy(p1.name, "阿迪王");
printf("%d %d %s", p1.age, p1.tall, p1.name);
return 0;
}

#include<stdio.h>
#include<string.h>
typedef myint;//自定义一个类型
typedef struct person
{
int age;
char name[15];
}person;//name要定义成数组形式,以便于下面对其的赋值,typedef将定义一个数据类型(struct到这行最后的那个花括号)


int main()                                           //然后花括号后面加一个自定义数据类型的名字,前面的结构体名可以省略
{      
myint a=10;//自定义数据类型可以当做一个形如int的类型
person p1={13, "qwewqe"};//为结构体赋值
printf("%d", a);                   
printf("%d  %s", p1.age, p1.name);                   
return 0;
}
//注:结构体定义结束后不能被用{}括起来的几个字面量整体赋值,但结构体变量之间可以相互赋值
}


{ //第56课 访问结构体成员
#include<stdio.h>
typedef struct person{
char name[10];//如果不是数组,那么下面scanf就要加&,其实是因为参数类型为指针类型
int age;
int tall;
int tz;
}person;


int main()
{
printf("请输入你的名字");
scanf("%s", p.name);//如果成员name是数组,那么就不需要&因为name就是数组的首地址
printf("请输入你的年龄");
scanf("%d", &p.age);
printf("请输入你的身高(cm)");
scanf("%d", &p.tall);
printf("请输入你的体重(kg)");
scanf("%d", &p.tz);
printf("名字\t年龄\t身高\t体重\n");

printf("%s\t%d\t%d\t%d\n", p.name, p.age, p.tall, p.tz);
return 0;
}
}


{ //第57课 结构体数组
#include<stdio.h>
typedef struct person{
char name[10];
int age;
int tall;
int tz;
}person;

int main()
{
person p[2];//数组元素的下标有两个,即0和1,是2就越界了!!
int i;
for(i=0;i<2;i++)
{
printf("请输入你的名字");
scanf("%s", p[i].name);//此时i=0但是访问的是第一个数组值
printf("请输入你的年龄");
scanf("%d", &p[i].age);//记住方括号要在结构体变量的后面,不能放在成员变量后面,否则就访问到成员变量的数组值了
printf("请输入你的身高(cm)");
scanf("%d", &p[i].tall);
printf("请输入你的体重(kg)");
scanf("%d", &p[i].tz);
}
printf("名字\t年龄\t身高\t体重\n");
for(i=0;i<2;i++)
{
printf("%s\t%d\t%d\t%d\n", p[i].name, p[i].age, p[i].tall, p[i].tz);
}
return 0;
}
}


{ //第58课 结构体指针   ->左边是指向结构体变量的指针,.的左边是结构体变量
#include<stdio.h>
typedef struct person{
char name[10];
int age;
int tall;
int tz;
}person;

int main()
{
person p={"www", 14, 160, 56};
person *pp=&p;//定义一个指向结构体变量的指针

printf("名字\t年龄\t身高\t体重\n");


printf("%s\t%d\t%d\t%d\n", pp->name, pp->age, pp->tall, pp->tz);//这里不能用小数点了,要用->

return 0;
}

#include<stdio.h>
typedef struct person{
char name[10];
int age;
int tall;
int tz;
}person;

int main()
{
person p;
person *pp=&p;

printf("请输入你的名字");
scanf("%s", pp->name);
printf("请输入你的年龄");
scanf("%d", &pp->age);//记住方括号要在结构体变量的后面,不能放在成员变量后面,否则就访问到成员变量的数组值了
printf("请输入你的身高(cm)");
scanf("%d", &pp->tall);//->左边是指向结构体变量的指针,.的左边是结构体
printf("请输入你的体重(kg)");
scanf("%d", &pp->tz);
printf("名字\t年龄\t身高\t体重\n");


printf("%s\t%d\t%d\t%d\n", pp->name, pp->age, pp->tall, pp->tz);

return 0;
}


#include<stdio.h>
typedef struct person{
char name[10];
int tall;
int age;
int tz;
}person;

int main()
{
int i;
person a[2];
person *p=a;
for(i=0;i<2;i++)
{
printf("请输入你的名字");
scanf("%s", p[i].name);
printf("请输入你的身高");
scanf("%d", &p[i].tall);
printf("请输入你的年龄");
scanf("%d", &p[i].age);
printf("请输入你的体重");
scanf("%d", &p[i].tz);
}
printf("名字\t身高\t年龄\t体重\n");
for(i=0;i<2;i++)
{
printf("%s\t%d\t%d\t%d\n", p[i].name, p[i].tall, p[i].age, p[i].tz);
}
return 0;
}
//注:->产生的由来(猜的,但便于理解记忆):若p是指向结构体变量var的指针,num为该结构体类型的一个数据项,则var.num等价于(*p).num
//    后者的()不可以省,因为已经规定.运算符的优先级比*高,于是通过指针访问数据项的这一种写法十分麻烦,这时候英雄->为此诞生了
}


{ //第59课 结构体做函数参数和结构体指针做函数参数
#include<stdio.h>
typedef struct person{
char name[10];
int tall;
int age;
int tz;
}person;

int i;
void show(person*);//这一句要写在结构体变量名定义的后面,不然person无法被识别
int main()
{
person a[2];
person *p=a;
for(i=0;i<2;i++)
{                                       
printf("请输入你的名字");
scanf("%s", p[i].name);//虽然前面将p定义为指向结构体变量的指针(按道理应该在后面用->)但是p连接了[],等效于*(p+i),所以要用“.”
printf("请输入你的身高");//可以将p换成a,其结果不变
scanf("%d", &p[i].tall);//可以回忆第32课的内容  指向数组的指针
printf("请输入你的年龄");
scanf("%d", &p[i].age);
printf("请输入你的体重");
scanf("%d", &p[i].tz);
}
show(p);
return 0;
}
void show(person *p)
{
printf("名字\t身高\t年龄\t体重\n");
for(i=0;i<2;i++)
{
printf("%s\t%d\t%d\t%d\n", p[i].name, p[i].tall, p[i].age, p[i].tz);
}
}


#include<stdio.h>
typedef struct person{
char name[10];
int tall;
int age;
int tz;
}person;


void show(person);
int main()
{
person pp={"www", 12, 43, 65};

show(pp);
printf("%s\t%d\t%d\t%d\n", pp.name, pp.tall, pp.age, pp.tz);//->左边是指向结构体变量的指针,.的左边是结构体
return 0;
}
void show(person pp)
{
pp.tall=100; //由于是传值,所以我们修改的是局部变量,此变量不会影响到其他函数的值,尽管pp的变量名相同,但是由于它们在不同的函数中,所以它们互不影响
printf("%s\t%d\t%d\t%d\n", pp.name, pp.tall, pp.age, pp.tz);//只修改了tall的值但是没修改main函数的tall的值
}



#include<stdio.h>
typedef struct person{
char name[10];
int tall;
int age;
int tz;
}person;


void show(person *);//形参跟着实参变
int main()
{
person pp={"www", 12, 43, 65};//pp是结构体变量

show(&pp);//由于要传地址所以把pp改成&pp
printf("%s\t%d\t%d\t%d\n", pp.name, pp.tall, pp.age, pp.tz);
return 0;
}
void show(person *pp)
{
pp->tall=100; //因为pp变成了指向结构体变量pp的地址的指针,所以把.变成->
printf("%s\t%d\t%d\t%d\n", pp->name, pp->tall, pp->age, pp->tz);
}//因为这次是传地址,所以修改的是与其他函数相同一份数据
//注:本科其实重点为指针作用的考察:结构体指针变量作函数参数,①参数值传递速率快得多,因为结构体通常较大;②可以通过指针间接访问和修改原变量
}




{ //第60课 把结构体保存到文件
#include<stdio.h>
#include<string.h>
typedef struct
{
char name[10];
int tall;
int age;
}person;


int main()
{
person pp={"ww", 170, 43};//这里的pp不是常量
fpos_t len;
FILE *fp=NULL;
fp=fopen("./zixue7.txt", "w");//由于用了w为模式字符,所以此文件在打开成功后字节数变为0原来的内容已经被删除
if(!fp)
{
printf("文件读取失败");
return 0;
}
fwrite(&pp, sizeof(pp), 1, fp);//写入内容到文件中,写入完成后文件内部指针停留在文件末尾,此函数会把结构体的内容保存到文件中
if(fgetpos(fp, &len)==0)//直接读取文件大小就可以了,因为此时的文件内部指针已经指向文件的末尾
{
printf("%d", len);
}


printf("名字:%s身高:%d体重:%d", pp.name, pp.tall,pp.age);
return 0;
}


}


{ //第61课 C语言之联合简介
#include <stdio.h>
typedef union test//test是联合体名
{
int a;
float b;
char c[10];//联合与结构体的区别是,结构体能在不同存储空间存储数据,而联合的数据都存在一个存储区中(不同时),相同点是可以存储不同类型的数据
}test;//这里的test是联合体变量名,用typedef处理后它可以当做一个类型使用(也可以说是别名)
int main()
{
test t;//定义联合名时要与赋值分开如果写成test t.a=10那么t.a将被视为一个联合名
t.a=10;
printf("%d", t.c[0]);//  由于联合体数据都占一个存储区所以它们输出的值都一样
  
return 0;
}

#include <stdio.h>
#include<string.h>
struct test{
union m{
char mj[10];
char mj1[10];
}m1;//这里的m像结构体名一样可以省略
}t={"www"};//t是结构体变量名,m1是联合体变量名
int main()
{
  
printf("%s", t.m1.mj);  //访问的时候先访问了结构体变量名,然后访问了联合体变量名,最后访问到联合体的成员变量
  
return 0;
}

#include<stdio.h>
#include<string.h>
struct myint{
union test{
char mj[10];
char mj1[10];
}m;//我们可以将结构体的成员写成联合体,但要注明联合体变量

union pp{
int a;
}p;
}t={"www", 10};//可以以赋值的方式将值分配给联合体变量,则联合体成员就被赋值了一个与联合体变量相同的值
int main()
{
printf("%s%d", t.m.mj, t.p.a);//依次访问到结构体->联合体->联合体成员变量
return 0;
}


#include<stdio.h>
#include<string.h>
union myint{
struct{
int tall;
int name[10];
}pp;
struct{
char c;
char a;
int b;
}aa;
}t={10};//由于是给联合体变量名t赋值,所以每一个结构体变量都有一个相同的存储空间,当我们访问到任意一个结构体时,该结构体的第一个成员变量就保存了结构体变量的
//值(因为t的值已经传给了每一个结构体变量)
int main()
{
printf("%d%d", t.aa.c, t.pp.tall);
return 0;
}


/*1)联合体是一个结构;
        2)它的所有成员相对于基地址的偏移量都为0;
        3)此结构空间要大到足够容纳最"宽"的成员;
        4)其对齐方式要适合其中所有的成员;
当多个数据需要共享内存或者多个数据每次只取其一时,可以利用联合体(union)。由于联合体中的所有成员是共享一段内存的,因此每个成员的存放首地址相对于于联合体变量
的基地址的偏移量为0,即所有成员的首地址都是一样的。为了使得所有成员能够共享一段内存,因此该空间必须足够容纳这些成员中最宽的成员。
对于这句“对齐方式要适合其中所有的成员”是指其必须符合所有成员的自身对齐方式。(注意对此段对话的理解)
union U1
{
    char s[10];
    int n;
    double d;
};
s占10字节,n占4字节,d占8字节,因此其至少需10字节的空间。然而其实际大小并不是10,用运算符sizeof测试其大小为16.这是因为这里存在字节对齐的问题,10既不能被4整除,也不能被8整除。因此补充字节到16,
这样就符合所有成员的自身对齐了。从这里可以看出联合体所占的空间不仅取决于最宽成员,还跟所有成员有关系,
即其大小必须满足两个条件:1)大小足够容纳最宽的成员;2)大小能被其包含的所有基本数据类型的大小所整除。
union U2
{    
    char s[5];
    int n;
    double d;
};
s占5字节,n占4字节,d占8字节,因此其至少需8字节的空间.而char型占用一个字节,int占用四个字节,double占用8字节,
其最小公倍数为8字节,所以U2也就是占用8字节就可以了。
下面结合实例予以说明:
下面是我整理的程序,在VS20O8下面编译通过:
#include


union myun
{
    struct
       {
          int x;
          int y;
          int z;
       }u;
    int k;
}a;


int main()
{
    a.u.x =4;
    a.u.y =5;
    a.u.z =6;
    a.k = 0;
    printf("%d %d %d\n",a.u.x,a.u.y,a.u.z);
    return 0;
}


这个例子在VC6.0中调试输出为0,5,6。该例程百度随处可见,非常典型,下面给出说明:
    union类型是共享内存的,以size最大的结构作为自己的大小,这样的话,myun这个结构就包含u这个结构体,而大小也等于u这个结构体的大小,
在内存中的排列为声明的顺序x,y,z从低到高,然后赋值的时候,在内存中,就是x的位置放置4,y的位置放置5,z的位置放置6。现在对k赋值,
对k的赋值因为是union,要共享内存,所以从union的首地址开始放置,首地址开始的位置其实是x的位置,这样原来内存中x的位置就被k所赋的值代替了,
就变为0了,这个时候要进行打印,就直接看内存里就行了,x的位置也就是k的位置是0,而y,z的位置的值没有改变,所以应该是0,5,6          */
}


{ //第62课 枚举类型
#include<stdio.h>
#include<string.h>


int main()
{
enum color{red=100, green=200, blue=300};//enum是枚举的关键字,color是枚举名,red是符号名称
enum color m;//枚举名形同则可以通过此方式对符号名称的值进行修改,也可以以此方式定义一个枚举变量并赋值,但是不可以全部以此方式进行,还需要上面的语句声明
m=red;
printf("%d\t", m);//所以m的值就是red的值
printf("%d", m);
return 0;
}


}


{ //第63课 指向函数的指针
#include<stdio.h>


void mychar(char);//自定义一个形参为字符类型的函数
void (*str)(char);//自定义一个指向字符类型函数的指针,其参数的个数与类型要与自定义函数的参数对应
int main()
{
str=mychar;//将自定义函数mychar的地址赋值给指针
char ch;
ch=getchar();
str(ch);//之后我们就可以用str代替mychar了
return 0;
}
void mychar(char p)
{

putchar(p-32);//常量32是ascii码中小写字母与大写字母之间转换的值,当大写字母加上32时就转换成了该字母的小写形式反之成立。
}

#include<stdio.h>


void mychar(char);
void myint(char);//由于是需要判断大写和小写两种情况,所以我们需要两个函数来分别处理
void (*str)(char);
int main()
{
char ch;
ch=getchar();
if(ch>='a' && ch<='z')
{
str=mychar;//当字母为小写时将指针指向处理小写字母的函数,这种方式节省了变形量
}else if(ch>='A' && ch<='Z')
{
   str=myint;
}
str(ch);//遇到小写字母时调用处理小写字母的函数,大写的一样。
return 0;
}
void mychar(char p)
{
putchar(p-32);
}
void myint(char p)
{
putchar(p+32);
}


#include<stdio.h>
#include<malloc.h>
void mychar(char);
void myint(char);
void (*s)(char);
int main()
{   int i;
    char *str=NULL;
str=(char *)malloc(10000);       
scanf("%s", str);//利用了缓冲输入原理
  for(i=0;*(str+i)!='\0';i++)//*(str+i)可以换成str[i],当i=0时取得字符串的第一个字符,用for语句依次读取字符串直到字符串结束
  {
if(*(str+i)>='a' && *(str+i)<='z')//同样的分类处理法
{
s=mychar;
}else if(*(str+i)>='A' && *(str+i)<='Z')
{
s=myint;
}
s(*(str+i));//调用函数输出其结果
  }
   free(str);
    return 0;
}
void mychar(char p)
{
p=p-32;
putchar(p);
}
void myint(char p)
{
p=p+32;
    putchar(p);
}
}


{ //第65课 位运算       (位操作)
//按位取反~
//格式:~(10011010)取反后(01100101)其实就是把一个字节里面的1变为0, 0变为1。

//位与&
//格式:(11110000)&(11001100)结果:(11000000)只有当两个操作位都为1,结果才为1

//位异或^
//格式:(10101010) ^ (01010101)结果:(11111111)对应位中如果只有一个为1,那么结果为1,也就是不能同时为1

//位或|
//格式:(11000100)|(01101101)结果:(11101101)操作位中,其中一位为1,结果为1,可以同时为1


#include<stdio.h>
int main()        
{                  
char ch=170;   
printf("%d, %d, %d, %d", ~ch, ch & 240, ch^85, ch | 240);//ch的按位取反的二进制结果为01010101(用计算器计算时会省略左边的一个0)。0xf0的二进制值为11110000,
return 0;                        //ch的二进制值为10101010,因为用了位与计算所以当对应的位都为1的时候结果为1,所以有10100000换算成十进制为160
}                                    //由于ch^85用了位异或计算,所以结果为11111111(补码)也是十进制的-1,因为-1的原码为10000001,反码为11111110
      // 85=01010101                 //补码为11111111(这是补码的-1,不是形式值255),由于用了%d的占位符,所以将二进制的补码转换成十进制的-1
 //170=10101010//ch | 240的二进制结果为:11111010(此结果为十进制中-6的补码,补码是真值的二进制码,不是形式值的二进制码)
 //240=11110000//真值是带有符号位的机器值所对应的真正数值,形式值是没有符号位的机器值,例如上面的有符号数 10000011,其最高位1代表负,
    //其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)

}


{ //第66课 打开位   用位操作转变字母大小写
    00100000
    //'b'=01100010//如果我们想通过位操作将小写字母变成大写的就需要把从最高为往最低位数第3位,如果是1就改为0,如果是0那么就已经是大写了所以不用管
    //'B'=01000010//可以发现大写字母与小写字母的区别就是第3位(从左往右数第3位)的值是0(是0就意味着这是大写字母,1就是小写的)
    //223=11011111//然后我们需要观察,将任意一对大小写字母的二进制码列出,就像上面写的一样,一位一位的对照,如果都是一样的数(都是0或都是1),
                  //就在对应位的下面写1不一样就写0,从此写成一个新的二进制码,用计算器算得223(不仅是这个栗子,如果用其他字母算的结果都一样)
#include<stdio.h>
char mychar(char);
int main()         

printf("请输入一个字符");
char c;
scanf("%c", &c);//假设我们输入了任意一个小写字母(我输入了a,其ascii码为1100001)
putchar(mychar(c));
return 0;
}                                   
char mychar(char ch)//a传给ch
{
return ch & 223;//将二进制码进行位与运算
}
  //223=11011111
  //'a'=01100001
  //位与运算将对应位上的数字都相同则位的结果为1,也就是只有一个0就让这个位为0,如果都为0也把此位定为1,则计算结果为01000001对应ascii码的65也就是A,于是就输出了A
  
  #include<stdio.h>
char mychar(char);
int main()         

printf("请输入一个字符");
char c;
scanf("%c", &c);            //这次我们需要把大写字母变成小写
putchar(mychar(c));         //假设我输入了大写字母P(二进制码为01010000)因为第三位为0,我们要把它变成1,所以我们原先用的223的二进制码已经不行了
return 0;                   //'P'=01010000
}                           // 32=00100000,其实这是按照上面方法的反方向想出来的       
char mychar(char ch)        //'p'=01110000,对应二进制码的112
{
return ch | 32;         //我们需要用位或计算(位异或也行但是位或只可以把大写字母变小写,反之不行),将对应位中只要有一个1的位变为1,如果都是0就变为0,
}                        //依此方式去掉了第三位的1。使得大写字母变成小写
}


{ //第67课 转置位及查看位
#include<stdio.h>
char mychar(char);
int main()         

printf("请输入一个字符");
char c;
scanf("%c", &c);            // 不同的是,这次我们使用了位异或(当对应位只有一个为1的时候结果为1,即两个为0或两个为1都不行),           
putchar(mychar(c));        
return 0;                   //'P'=01010000
}                           // 32=00100000  
char mychar(char ch)        //'p'=01110000
{
return ch ^ 32;         
}
//由观察上面的二进制码可以发现,如果我们用位或操作时,的确可以将大写字母变成小写,但是却不能把小写变成大写,因为比如'p'的二进制码为01110000
//与00100000进行位或计算后结果还是01110000没有变化,而位异或则在保证大写变小写结果不变的基础上,可以让对应位中都为1的变成0,保留只有一个为1的,
//所以可以使得第三位的1变成0(因为只有第三位的值与小写字母的二进制码都为1,但是其他的位都没有对应的两个1)







}


{ //第68课 左移右移 (大结局)
#include<stdio.h>
int main()         
{                 
char ch=100;     //100=01100100
printf("%d", ch<<2); 
return 0;                   
}                
//左移的符号:<<  功能:将二进制码全部左移指定的位数,如果移动后超过了总位数,则将超出的位值去掉,并在右边多出的位中填充0
//右移的符号:>>  功能:将二进制码全部右移指定的位数,如果移动后超过了总位数,则将超出的位值去掉,并在左边多出的位中填充0
//例如100的二进制码为01100100,现在我们左移1位,由于此时并没有超过总位数,所以并没有去掉左边的任何值,但是会在二进制码的末尾也就是右边填充0
//所以我们可以得到11001000,此值对应十进制的200,输出正好可以得到此值。


#include<stdio.h>
int main()         
{                 
 char ch=100;     //100=01100100
printf("%d", ch>>1); 
return 0;                   
}   
//现在我们右移1位,同样我们100的二进制码为01100100,右移后由于超过了位数的界限,所以去掉了末尾的一个0(一个二进制码的左边的界限比右边的大许多,这是因为
//二进制码是紧靠右边界限的,右边界限就是二进制码的最低位),则二进制码变为110010,对应十进制的50
//对比分别左移1位2位,100,右移1位2位可以发现结果呈现出25-50-100-200-400这样的依次乘2的特性,且左移一位,结果必定在原数的基础上乘2,右移则是除2
//所以我们可以通过此特性进行指数运算,比如2^2(二的二次方)就可以通过将>>左边的数定为2,右边的定为2-1(如果是2的三次方就3-1)由此输出其结果


#include<stdio.h>
int main()         
{                 
int c=100, i=0;     //100=01100100
                    //128=10000000
for(i=0;i<8;i++)    //100=01100100
{
if((c<<i) & 128)putchar('1');
else putchar('0');
}
return 0;                   
}                

}


{ //附录 
{  
 getch();   
可以在return 0;的前面一句加  getch();  防止生成的exe格式文件一闪而过。
把c格式文件生成exe格式文件的方法为:
在cmd控制台编译通过后键入:     gcc zixue7.c -o zixue7.exe  就行了
如果出现以下结果:


C:\c>gcc zixue7.c -o zixue7.exe
c:/mingw/mingw/bin/../lib/gcc/mingw32/4.6.2/../../../../mingw32/bin/ld.exe: cann
ot open output file zixue7.exe: Permission denied
collect2: ld returned 1 exit status


这说明 zixue7.exe这个文件已经存在了,我们解决的方法是把 zixue7这个标题改成zixue71,或者怎么改都行,只要不与前面的重复。也可能是没有保存文件。Ctrl+s保存
}


******************************************************************************************************************************************************************
{
double类型数与float类型的数的区别
一般是精度的不同,double类型数比float类型精度高但占的空间大
一般情况都是差不多的, 不过大部分情况都定义为double


主要是因为常小数值  系统识别为double  这样在计算时 免去类型转化  ,总之无所谓


但需要注意的是定义double类型数并用scanf语句进行赋值的时候,占位符通常用%lf,(%后面的是字母L)但是定义float类型数只需要用%f就行了


}


&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
{
if, while, for和do while的区别
if一般用在判断变量数字或字符代表的十进制值的大小是否满足要求,如果满足就执行{}中的语句不满足就执行{}下面的语句
while语句同样可以用来判断上述的值是否满足要求,当括号里面的东西满足要求就执行{}中的语句不满足就执行{}下面的语句
for语句一般用在循环嵌套中,从而构成比较复杂的循环语句结构为  for(初始化;条件;改变数据)然后后面加{}并在里面加上内容就行了
do while 语句与while语句的区别在于前者是先执行后判断,而后者相反,相同点在于都是在括号里面的条件不满足时退出循环,需要注意do while语句的括号后面有分号


}


^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
{
源代码文件变成可执行文件的过程
首先,我们在编译器上编写的代码,称做源代码,而他们的集合称之为源代码文件(其扩展名为c),编译器会对这些源代码进行编译,从而将源代码变成了计算机可以理解的语言,
也就是中间代码,也可以称之为机器语言代码,其结果被放在目标代码文件中(扩展名为obj),然而这些代码还不能被执行,我们还需要被称之为连接器的东西,将库代码和启动代
码链接在一起,并放入扩展名为exe的可执行文件夹中,一般先默认为a.exe然后就可以被计算机理解并执行了,库代码文件中的代码,包含了你需要的函数的目标代码。
而链接器只对你需要的进行调用,至于启动代码,它是程序与操作系统的接口。




}


$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
{
自定义函数
这指的是定义一个库文件中没有的函数代码一下,以下是代码示例:

#include<stdio.h>
void apple(); //这里需要在括号后面加分号,apple前面的void表示返回的值为空,void在英语中是空的、或者是虚空的意思,这句话告诉编译器你在使用特殊函数
int main ()
{
printf("how many apple do you have?\n");
apple();//定义的特殊函数在这里被调用
printf("so you have a lot of apples\n ");
    return 0;
}
void apple(void)//跟定义main函数一样需要在下面加上花括号并在其中添加你想要的内容
{
printf("I have three apples\n");
}
 
//函数apple()的定义也可以放在int main()的前面,并不影响运行的效果


}


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
{
定义取值范围不同的整型变量
我们最为熟悉的整形变量为int,但是还有一些可以定义取值范围比int大的整形变量,例如:long int, long long int,还有一个专门定义非负数的形式:unsigned,他的取值范围
包括0,但是表示正数的部分比int形大的多,还有以此为基础的unsigned long,unsigned long long类型等,他们可以比int形存储更大范围的数,但是在计算时,会比较慢
还有比int存储范围小的类型:short int类型和专门表示非负数的类型:unsigned short,它们的存储范围通常比int小,可以用来定义比较小的数,需要注意,由于系统位数的不同
定义上述的整数类型时会出现差异,C语言只保证long形不比int形短,short形不比int形长,这是为了适应不同的机型的需要


以下是补充的内容:
#include<stdio.h>
int main()
{
 double a=0;
 printf("请输入分数:");
 scanf("%lf", &a);//这里的lf中f前面的不是数字一,而是英文字母l且为小写,如果把double类型变成float类型就一定要去掉l
if(a>=0 && a<=20)//if后的括号没有';'号但都引导了一个循环体
{
printf("成绩等级为:D");
}else if(a>20 && a<=40)
{
printf("成绩等级为:C");
}else if(a>40 && a<=60)
{    
printf("成绩等级为:B");
}else if(a>60 && a<=80)
{
printf("成绩等级为:A");
}else if(a>80 && a<=100)
{
printf("成绩等级为:S");
}else if(a>100 && a<=120)
{
printf("成绩等级为:ss");
}else {printf("请输入正确的分数");
}


return 0;
}
/*为什么要在给双精度的浮点数类型赋值的时候在%f之间加字母l呢,因为double类型比float类型精度高,但占了更大的空间,所以在给双精度浮点数类型赋值的时候
要把那个值当做long形对待,如果要输出此类型的数,如printf("%lf", a);应该在f前面加字母l因为这可用保证此程序在别的机型上正常运行,但是也可以不加,
因为一般不会有太大的区别*/


}


!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{
可移植类型:inttypes.h


#include<stdio.h>
#include<inttypes.h>
int main(void)
{
int16_t me16;

me16=4593;
printf("first, assume int16_t is short:");
printf("me16 =%d\n", me16);
printf("Next, let us not make any assumptions.\n");
printf("instead, use a \"macro\" from inttypes.h: ");
    printf("me16 = %" PRId16 "\n", me16);//这里的PRId16可以换成"hd"   PRId16中的d是小写,其他的字母是大写
return 0;
}
//补充一下,inttypes.h头文件将定义串PRId16来表示打印16位有符号值所需的合适说明符(例如,hd,或d)
}


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{
字符画一张


#include<stdio.h>
int main(void)
{
        


        printf("                                               .&______~*@*~______&.                            *        \n");                             
        printf("                                                  ^w/\%\%\%\%\%\%\%\%\%\%\%\%\%\%\%\%\%\%\%\\w^                              ***      \n");                            
        printf("                                               \"Y\"\"Y\"\"Y\"\"\"\"\"Y\"\"Y\"\"Y\"                          *****   \n");                            
        printf("                                            p-p_|__|__|_____|__|__|_q-q                       **Y**    \n");                              
        printf("                                           [EEEEM==M==MM===MM==M==MEEEE]                    ....|....   \n");               
              
return 0;
}






}


~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
{
缓冲输入和非缓冲输入
//首先让我们先了解什么是缓冲区,它是一个临时存储区域,我们在打字的时候会把键入的字符存入缓冲区中当我们按回车的时候会让缓冲区的字符变的对程序可用,
也就是把你输入的字符注入程序中(我们不管你输入的东西是否会回显),缓冲区是比较大的,一般为512字节或4096字节
了解完缓冲区我们进入对缓冲输入的了解,缓冲输入又分为两类,完全缓冲I/O和行(hang)缓冲I/O(I/O的英文意思是intput和output即输入输出),
对于完全缓冲输入,缓冲区被填满时被清空(内容被发送到其目的地)这种类型的缓冲一般用于文件输入中,而行缓冲I/O在遇到一个换行字符的时候清空缓冲区(当然如果缓冲区
被填满也会被清空)比如我现在在输入文字那么按下回车键就是进行了这样的一次操作,你可能会问清空缓冲区会怎么样,它们会对程序变得可用。
对于非缓冲输入,没有什么缓冲区可言,在你输入字符后,它就立即对程序可用。


下面我通过几个程序说明它们的区别,为了说明清楚我对特定的语句进行了标号:


#include<stdio.h>
int main()
 {
  char ch;//    1
 ch =getchar();//     2
  while(ch != '#') //         3
   {
  putchar(ch);//      4
   ch = getchar();//     5
   }
printf("%c\n", ch);//    6
                
return 0;
 }        
 /*第1句,我们定义了一个字符类型变量ch,假设我现在输入了qwertyuiop这一串字符,并按下回车键(不是字符串,因为在读取时是作为单个字符读取的),
 接着,通过第2句接收了一个字符(注意,现在在缓冲区中的数据已被清空,但只有字符p暂时被读取)这个字符是q,千万不要以为第2句读取了qwertyuiop这整串字符,
 然后到了第3句,很明显这个字符是p,而不是#所以可以进入循环然后,通过第4句输出了这个字符p(更专业一点我们称之为回显)到第5句了,现在我们读取了下一个字符w,
 现在继续回到第3句循环的判断中,w不是#所以又会继续下去,由于 qwertyuiop没有任何一个字符是#,所以会在下面输出完全跟你输入的一样的一串字符,下面是运行结果:
 
 qwertyuiop
 qwertyuiop
 如果我输入qwe#aas这样一串字符那么会有如下结果:
 
 qwe#aas
 qwe#
 因为在读取到#这个字符时第3句的循环条件被打断,所以进行到第6句,用来输出#,后面的字符由于没有语句对其进行读取,所以不会回显
 终上所述,getchar语句(不是函数)会读取你输入的第一个字符(不管你是否输入一串字符),并且可以在下面回显,如果你愿意,可以把第2句换成scanf("%c", &ch);
 把第4句换成printf("%c", ch);第5句换成scanf("%c", &ch);此语句多用于行缓冲输入*/
 
 #include<stdio.h>
int main()
 {
  char ch;//    1
 ch =getch();//     2
  while(ch != '#') //         3
   {
  putchar(ch);//      4
   ch = getch();//     5
   }
printf("%c\n", ch);//    6
                
return 0;
 }   
 /*这是第二个程序,我用它来描述getch语句,假设我输入了1234,然后我键入回车键再输入5678,第1句同上,第2句获取了第一个数字1,由于没有缓冲区,所以直接被程序使用
 到第3句判断是否是#,很明显不是的,然后进入循环由于是getch语句,所以无法回显所以并没有像上面一样,出现相同的字符,然后在第5句读取下一个字符,这个过程
 非常快,你输入完1的时候已经运行到这了,然后按2,并完成输入1234的过程,现在我们按了回车,但是光标并没有到下面的一行去,而是回到了本行的开头处,
 为什么呢?不要忘了你按的回车键其实也是字符,回车字符被getch读取成ascii码的13,即转义字符的\r,让光标回到本行开始处,下面输入的5678会覆盖在原来的结果上
 如果再输入一个#,那么就会跳出循环并执行到第6句由于是getch语句,所以不会回显*/
 
 /*所以getch语句和getchar语句的区别是:前者不能回显而后者可以,再就是前者多用于非缓冲输入而后者相反,再强调一下Enter键也是字符!




}


………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………
{什么是首地址


“首地址”一词不是C语言里的术语。是计算机原理里面的概念。
计算机存储器(主要指内存储器)的每个单元都有一个编号,称作地址,CPU就是靠这个编号——地址来访问(即读、写)每个单元。


而程序中的变量,有些需要不止一个单元来存放。例如C语言中的long型变量,通常都是32位二进制数,在每个单元都是8位的计算机存储器中,需要4个单元才能存放一个long型的数。
为了便于访问,一个变量所占用的这几个单元通常都连续存放,也就是说,占用地址连续的几个单元。地址连续的几个单元通常就叫做一个“存储区域”。


这样说,就该明白什么叫“首地址”了吧?就是该变量所占的存储区域中的第一个单元的地址。
}


++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
{指针对字符串的操作


#include<stdio.h> 
int main()
{
char *str="www.zixue7.com";  
printf("%c", *(str+5));  /*str是刚才定义的字符串的首地址,现在由于括号的优先级高于*所以先算括号里面的首地址+5,访问到“.”所在的地址现在括号里面的算完了
然后算*号,这里*不是上面定义指针的意思,而是访问指针指向地址的值就是“.”的ascii码105,输出“.”是因为左边的占位符是%c
return 0;
}


#include<stdio.h> 
int main()
{
char *str="www.zixue7.com";  
str+=4;//这里相当于str=str+4;是对地址的操作,由此可见通过加减首地址可以改变访问字符的顺序
while(*str != '\0')//www.zixue7.com这串字符的后面其实还暗藏了\0来表示字符串的结尾
{
putchar(*str);//我们需要输出的是指针指向的值,而不是地址所以要用*str
str++;//由于需要依次输出每个字符,所以要每次把地址加1
}
return 0;
}


#include<stdio.h> 
int main()
{
char p[123]="www.zixue7.com";//  1
char *str=p;//    2
*(str+10)='\0';//  3
str+=4;
while(*str != '\0')
{
putchar(*str);
str++;
}
return 0;
}


/*你也许会问为什么不去掉第1句把第2句改成char *str="www.zixue7.com";呢?其实我们是需要通过第3句对字符串的内容进行改动对吧,但是不要忘了这种方法定义的是字符常量
所以不能随意进行更改,只有变量才能满足这样的要求,所以第1句是不可缺少的






}


###############################################################################################################################################################
{
与数组指针关系
数组指针是指向数组首元素的地址的指针,其本质为指针(这个指针存放的是数组首地址的地址,相当于2级指针,这个指针不可移动);指针数组是数组元素为指针的数组,
其本质为数组。例如:*p[]是指针数组,实质是一个数组,里面的两个元素都是指针[]的优先级比*的优先级高,


p先与[]结合,形成数组p,有两个元素的数组,再与*结合,表示此数组是指针类型的,每个数组元素相当于一个指针变量


而(*p)[]则为数组指针,实质是一个指针,因为有了(),所以p先与*结合形成指针,再与[]结合,表示是指向整个这个数组的指针。
}


))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
{原码反码补码

一. 机器数和真值
在学习原码, 反码和补码之前, 需要先了解机器数和真值的概念.


1、机器数


一个数在计算机中的二进制表示形式,  叫做这个数的机器数。机器数是带符号的,在计算机用一个数的最高位存放符号, 正数为0, 负数为1.


比如,十进制中的数 +3 ,计算机字长为8位,转换成二进制就是00000011。如果是 -3 ,就是 10000011 。


那么,这里的 00000011 和 10000011 就是机器数。


2、真值


因为第一位是符号位,所以机器数的形式值就不等于真正的数值。例如上面的有符号数 10000011,其最高位1代表负,
其真正数值是 -3 而不是形式值131(10000011转换成十进制等于131)。所以,为区别起见,将带符号位的机器数对应的真正数值称为机器数的真值。


例:0000 0001的真值 = +000 0001 = +1,1000 0001的真值 = –000 0001 = –1


 


二. 原码, 反码, 补码的基础概念和计算方法.
在探求为何机器要使用补码之前, 让我们先了解原码, 反码和补码的概念.对于一个数, 计算机要使用一定的编码方式进行存储. 
原码, 反码, 补码是机器存储一个具体数字的编码方式.


1. 原码


原码就是符号位加上真值的绝对值, 即用第一位表示符号, 其余位表示值. 比如如果是8位二进制:


[+1]原 = 0000 0001


[-1]原 = 1000 0001


第一位是符号位. 因为第一位是符号位, 所以8位二进制数的取值范围就是:


[1111 1111 , 0111 1111]





[-127 , 127]


原码是人脑最容易理解和计算的表示方式.


2. 反码


反码的表示方法是:


正数的反码是其本身


负数的反码是在其原码的基础上, 符号位不变,其余各个位取反.


[+1] = [00000001]原 = [00000001]反


[-1] = [10000001]原 = [11111110]反


可见如果一个反码表示的是负数, 人脑无法直观的看出来它的数值. 通常要将其转换成原码再计算.


3. 补码


补码的表示方法是:


正数的补码就是其本身


负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后+1. (即在反码的基础上+1)


[+1] = [00000001]原 = [00000001]反 = [00000001]补


[-1] = [10000001]原 = [11111110]反 = [11111111]补


对于负数, 补码表示方式也是人脑无法直观看出其数值的. 通常也需要转换成原码在计算其数值.


 


三. 为何要使用原码, 反码和补码
在开始深入学习前, 我的学习建议是先"死记硬背"上面的原码, 反码和补码的表示方式以及计算方法.(额,,我不建议,通常都是理解之后才更容易记住吧)


现在我们知道了计算机可以有三种编码方式表示一个数. 对于正数因为三种编码方式的结果都相同:


[+1] = [00000001]原 = [00000001]反 = [00000001]补


所以不需要过多解释. 但是对于负数:


[-1] = [10000001]原 = [11111110]反 = [11111111]补


可见原码, 反码和补码是完全不同的. 既然原码才是被人脑直接识别并用于计算表示方式, 为何还会有反码和补码呢?


首先, 因为人脑可以知道第一位是符号位, 在计算的时候我们会根据符号位, 选择对真值区域的加减. (真值的概念在本文最开头).
但是对于计算机, 加减乘数已经是最基础的运算, 要设计的尽量简单. 计算机辨别"符号位"显然会让计算机的基础电路设计变得十分复杂! 
于是人们想出了将符号位也参与运算的方法. 我们知道, 根据运算法则减去一个正数等于加上一个负数,
即: 1-1 = 1 + (-1) = 0 , 所以机器可以只有加法而没有减法, 这样计算机运算的设计就更简单了.
 
于是人们开始探索 将符号位参与运算, 并且只保留加法的方法. 首先来看原码:


计算十进制的表达式: 1-1=0


1 - 1 = 1 + (-1) = [00000001]原 + [10000001]原 = [10000010]原 = -2


如果用原码表示, 让符号位也参与计算, 显然对于减法来说, 结果是不正确的.这也就是为何计算机内部不使用原码表示一个数.


为了解决原码做减法的问题, 出现了反码:


计算十进制的表达式: 1-1=0


1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原= [0000 0001]反 + [1111 1110]反 = [1111 1111]反 = [1000 0000]原 = -0


发现用反码计算减法, 结果的真值部分是正确的. 而唯一的问题其实就出现在"0"这个特殊的数值上. 


虽然人们理解上+0和-0是一样的, 但是0带符号是没有任何意义的.  而且会有[0000 0000]原和[1000 0000]原两个编码表示0.于是补码的出现, 解决了0的符号以及两个编码的问题:


1-1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [0000 0001]补 + [1111 1111]补 = [0000 0000]补=[0000 0000]原


这样0用[0000 0000]表示, 而以前出现问题的-0则不存在了.而且可以用[1000 0000]表示-128:


(-1) + (-127) = [1000 0001]原 + [1111 1111]原 = [1111 1111]补 + [1000 0001]补 = [1000 0000]补


-1-127的结果应该是-128, 在用补码运算的结果中, [1000 0000]补 就是-128. 但是注意因为实际上是使用以前的-0的补码来表示-128, 所以-128并没有原码和反码表示.
(对-128的补码表示[1000 0000]补算出来的原码是[0000 0000]原, 这是不正确的)使用补码, 不仅仅修复了0的符号以及存在两个编码的问题,


 而且还能够多表示一个最低数. 这就是为什么8位二进制, 使用原码或反码表示的范围为[-127, +127], 而使用补码表示的范围为[-128, 127].


因为机器使用补码, 所以对于编程中常用到的32位int类型, 可以表示范围是: [-2^31, 2^31-1] 因为第一位表示的是符号位.而使用补码表示时又可以多保存一个最小值.


 


四 原码, 反码, 补码 再深入
计算机巧妙地把符号位参与运算, 并且将减法变成了加法, 背后蕴含了怎样的数学原理呢?


将钟表想象成是一个1位的12进制数. 如果当前时间是6点, 我希望将时间设置成4点, 需要怎么做呢?我们可以:


1. 往回拨2个小时: 6 - 2 = 4


2. 往前拨10个小时: (6 + 10) mod 12 = 4


3. 往前拨10+12=22个小时: (6+22) mod 12 =4


2,3方法中的mod是指取模操作, 16 mod 12 =4 即用16除以12后的余数是4.


所以钟表往回拨(减法)的结果可以用往前拨(加法)替代!


现在的焦点就落在了如何用一个正数, 来替代一个负数. 上面的例子我们能感觉出来一些端倪, 发现一些规律. 但是数学是严谨的. 不能靠感觉.


首先介绍一个数学中相关的概念: 同余


 


同余的概念


两个整数a,b,若它们除以整数m所得的余数相等,则称a,b对于模m同余


记作 a ≡ b (mod m)


读作 a 与 b 关于模 m 同余。


举例说明:


4 mod 12 = 4


16 mod 12 = 4


28 mod 12 = 4


所以4, 16, 28关于模 12 同余.


 


负数取模


正数进行mod运算是很简单的. 但是负数呢?


下面是关于mod运算的数学定义:


clip_image001


上面是截图, "取下界"符号找不到如何输入(word中粘贴过来后乱码). 下面是使用"L"和"J"替换上图的"取下界"符号:


x mod y = x - y L x / y J


上面公式的意思是:


x mod y等于 x 减去 y 乘上 x与y的商的下界.


以 -3 mod 2 举例:


-3 mod 2


= -3 - 2xL -3/2 J


= -3 - 2xL-1.5J


= -3 - 2x(-2)


= -3 + 4 = 1


所以:


(-2) mod 12 = 12-2=10


(-4) mod 12 = 12-4 = 8


(-5) mod 12 = 12 - 5 = 7


 


开始证明


再回到时钟的问题上:


回拨2小时 = 前拨10小时


回拨4小时 = 前拨8小时


回拨5小时= 前拨7小时


注意, 这里发现的规律!


结合上面学到的同余的概念.实际上:


(-2) mod 12 = 10


10 mod 12 = 10


-2与10是同余的.


(-4) mod 12 = 8


8 mod 12 = 8


-4与8是同余的.


距离成功越来越近了. 要实现用正数替代负数, 只需要运用同余数的两个定理:


反身性:


a ≡ a (mod m)


这个定理是很显而易见的.


线性运算定理:


如果a ≡ b (mod m),c ≡ d (mod m) 那么:


(1)a ± c ≡ b ± d (mod m)


(2)a * c ≡ b * d (mod m)


如果想看这个定理的证明, 请看:http://baike.baidu.com/view/79282.htm


所以:


7 ≡ 7 (mod 12)


(-2) ≡ 10 (mod 12)


7 -2 ≡ 7 + 10 (mod 12)


现在我们为一个负数, 找到了它的正数同余数. 但是并不是7-2 = 7+10, 而是 7 -2 ≡ 7 + 10 (mod 12) , 即计算结果的余数相等.


接下来回到二进制的问题上, 看一下: 2-1=1的问题.


2-1=2+(-1) = [0000 0010]原 + [1000 0001]原= [0000 0010]反 + [1111 1110]反


先到这一步, -1的反码表示是1111 1110. 如果这里将[1111 1110]认为是原码, 则[1111 1110]原 = -126, 这里将符号位除去, 即认为是126.


发现有如下规律:


(-1) mod 127 = 126


126 mod 127 = 126


即:


(-1) ≡ 126 (mod 127)


2-1 ≡ 2+126 (mod 127)


2-1 与 2+126的余数结果是相同的! 而这个余数, 正式我们的期望的计算结果: 2-1=1


所以说一个数的反码, 实际上是这个数对于一个膜的同余数. 而这个膜并不是我们的二进制, 而是所能表示的最大值! 这就和钟表一样, 
转了一圈后总能找到在可表示范围内的一个正确的数值!


而2+126很显然相当于钟表转过了一轮, 而因为符号位是参与计算的, 正好和溢出的最高位形成正确的运算结果.


既然反码可以将减法变成加法, 那么现在计算机使用的补码呢? 为什么在反码的基础上加1, 还能得到正确的结果?


2-1=2+(-1) = [0000 0010]原 + [1000 0001]原 = [0000 0010]补 + [1111 1111]补


如果把[1111 1111]当成原码, 去除符号位, 则:


[0111 1111]原 = 127


其实, 在反码的基础上+1, 只是相当于增加了膜的值:


(-1) mod 128 = 127


127 mod 128 = 127


2-1 ≡ 2+127 (mod 128)


此时, 表盘相当于每128个刻度转一轮. 所以用补码表示的运算结果最小值和最大值应该是[-128, 128].


但是由于0的特殊情况, 没有办法表示128, 所以补码的取值范围是[-128, 127]



}


6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666
{
位运算是指按二进制进行的运算。在系统软件中,常常需要处理二进制位的问题。C语言提供了6个位操作运算符。这些运算符只能用于整型操作数,
即只能用于带符号或无符号的char,short,int与long类型。
C语言提供的位运算符列表:
运算符含义描述
& 按位与      如果两个相应的二进制位都为1,则该位的结果值为1,否则为0
| 按位或      两个相应的二进制位中只要有一个为1,该位的结果值为1
^ 按位异或    若参加运算的两个二进制位值相同则为0,否则为1
~ 取反        ~是一元运算符,用来对一个二进制数按位取反,即将0变1,将1变0
<< 左移       用来将一个数的各二进制位全部左移N位,右补0
>> 右移       将一个数的各二进制位右移N位,移到右端的低位被舍弃,对于无符号数,高位补0


1、“按位与”运算符(&)


    按位与是指:参加运算的两个数据,按二进制位进行“与”运算。如果两个相应的二进制位都为1,


则该位的结果值为1;否则为0。这里的1可以理解为逻辑中的true,0可以理解为逻辑中的false。按位与其


实与逻辑上“与”的运算规则一致。逻辑上的“与”,要求运算数全真,结果才为真。若,


A=true,B=true,则A∩B=true 例如:3&5 3的二进制编码是11(2)。(为了区分十进制和其他进制,本文规


定,凡是非十进制的数据均在数据后面加上括号,括号中注明其进制,二进制则标记为2)内存储存数据


的基本单位是字节(Byte),一个字节由8个位(bit)所组成。位是用以描述电脑数据量的最小单位。二


进制系统中,每个0或1就是一个位。将11(2)补足成一个字节,则是00000011(2)。5的二进制编码是


101(2),将其补足成一个字节,则是00000101(2)
按位与运算:
 00000011(2)
&00000101(2)
 00000001(2)
由此可知3&5=1
c语言代码:
#include <stdio.h>
main()
{
 int a=3;
 int b = 5;
 printf("%d",a&b);
}
按位与的用途:
(1)清零
若想对一个存储单元清零,即使其全部二进制位为0,只要找一个二进制数,其中各个位符合一下条件:


原来的数中为1的位,新数中相应位为0。然后使二者进行&运算,即可达到清零目的。
例:原数为43,即00101011(2),另找一个数,设它为148,即10010100(2),将两者按位与运算:
 00101011(2)
&10010100(2)
 00000000(2)
c语言源代码:
#include <stdio.h>
main()
{
 int a=43;
 int b = 148;
 printf("%d",a&b);
}
(2)取一个数中某些指定位
若有一个整数a(2byte),想要取其中的低字节,只需要将a与8个1按位与即可。
a 00101100 10101100
b 00000000 11111111
c 00000000 10101100
(3)保留指定位:
与一个数进行“按位与”运算,此数在该位取1.
例如:有一数84,即01010100(2),想把其中从左边算起的第3,4,5,7,8位保留下来,运算如下:
 01010100(2)
&00111011(2)
 00010000(2)
即:a=84,b=59
    c=a&b=16
c语言源代码:
#include <stdio.h>
main()
{
 int a=84;
 int b = 59;
 printf("%d",a&b);
}


2、“按位或”运算符(|)
两个相应的二进制位中只要有一个为1,该位的结果值为1。借用逻辑学中或运算的话来说就是,一真为真



例如:60(8)|17(8),将八进制60与八进制17进行按位或运算。
 00110000
|00001111
 00111111
c语言源代码:
#include <stdio.h>
main()
{
 int a=060;
 int b = 017;
 printf("%d",a|b);
}
应用:按位或运算常用来对一个数据的某些位定值为1。例如:如果想使一个数a的低4位改为1,则只需要


将a与17(8)进行按位或运算即可。


3、“异或”运算符(^)
他的规则是:若参加运算的两个二进制位值相同则为0,否则为1
即0∧0=0,0∧1=1,1∧0=1, 1∧1=0
    例:   00111001
        ∧ 00101010
           00010011
c语言源代码:
#include <stdio.h>
main()
{
 int a=071;
 int b = 052;
 printf("%d",a^b);
}
应用:
(1)使特定位翻转
设有数01111010(2),想使其低4位翻转,即1变0,0变1.可以将其与00001111(2)进行“异或”运算,


即:
 01111010
^00001111
 01110101
运算结果的低4位正好是原数低4位的翻转。可见,要使哪几位翻转就将与其进行∧运算的该几位置为1


即可。
(2)与0相“异或”,保留原值
例如:012^00=012
        00001010
       ^00000000
        00001010
因为原数中的1与0进行异或运算得1,0^0得0,故保留原数。
(3) 交换两个值,不用临时变量
例如:a=3,即11(2);b=4,即100(2)。
想将a和b的值互换,可以用以下赋值语句实现:
    a=a∧b;
    b=b∧a;
    a=a∧b;
a=011(2)
    (∧)b=100(2)
a=111(2)(a∧b的结果,a已变成7)
    (∧)b=100(2)
b=011(2)(b∧a的结果,b已变成3)
    (∧)a=111(2)




a=100(2)(a∧b的结果,a已变成4)
等效于以下两步:
    ① 执行前两个赋值语句:“a=a∧b;”和“b=b∧a;”相当于b=b∧(a∧b)。
    ② 再执行第三个赋值语句: a=a∧b。由于a的值等于(a∧b),b的值等于(b∧a∧b),


因此,相当于a=a∧b∧b∧a∧b,即a的值等于a∧a∧b∧b∧b,等于b。
很神奇吧!
c语言源代码:
#include <stdio.h>
main()
{
 int a=3;
 int b = 4;
 a=a^b;
 b=b^a;
 a=a^b;
 printf("a=%d b=%d",a,b);
}


4、“取反”运算符(~)
他是一元运算符,用于求整数的二进制反码,即分别将操作数各二进制位上的1变为0,0变为1。
例如:~77(8)
源代码:
#include <stdio.h>
main()
{
 int a=077;
 printf("%d",~a);
}



}
1 0