一、初识C语言从常量变量开始
1.Bug和Debug为什么叫抓虫?mare2研发中有一只在继电器中,抓出来就可以运行了。
2.sizeof运算符计算变量占用内存空间的大小;sizeof(int)=4;sizeof(a+b);sizeof(a);
3.宏定义与const常量(constant):
(1)宏常量(Macro Constant):用一个标识符号来表示常量
宏定义:#define 标识符 字符串 //#表示这是编译预处理命令
#define PI 3.14159
#define R 5.3
缺点:只是进行简单的字符串替换而不进行类型检查,因此会经常发生意想不到的错误
(2)用const常量来替换宏常量 //放在只读存储器中,在定义的时候赋初值
#include <stdio.h>
int main()
{
const double pi=3.14159;
const double r=5.3;
printf("area=%f\n",pi*r*r);
return 0;
}
4.数字在计算机中以二进制存储,负数以二进制补码表示(为什么负数在计算机中要以补码的形式表示?)
计算机内部用什么方式表示负数,其实是无所谓的。只要能够保持一一对应的关系,就可以用任意方式表示负数。所以,既然可以任意选择,那么理应选择一种最方便的方式。
2的补码就是最方便的方式。它的便利体现在,所有的加法运算可以使用同一种电路完成。
5.数据和内存
1字节(byte)=8位(bit)
二、做点计算
1.算术运算
2.赋值运算
3.增1和减1运算符
4.自动类型转换
5.强制类型转换
(类型)表达式 aver=(float)total/number;
三、键盘输入屏幕输出
1.输出
包含头文件#include <stdio.h>
scanf()--接收输入
printf()--打印输出
2.数据的格式化键盘输入
scanf(格式控制字符串,输入地址表);
scanf("%d %f",&a,&b); //别忘了区地址运算符
scanf("%2d%2d",&a,&b); //指定位宽2 1234
scanf("%d,%f",&a,&b); //12,34
scanf("a=%d,b=%f",&a,&b); //a=12,b=34
scanf()的返回值=正确读入的数据项数
3.单个字符的输入输出
字符在内存中以对应的ascii码的二进制值进行存储。a-065 A-097
字符输出函数putchar()
putchar(ch)
字符输入函数getchar()
ch=getchar()
从键盘接收的字符作为getchar()的函数值,没有参数
eg:大写英文字母转换为小写英文字母:
#include <stdio.h>
int main()
{
char ch;
ch=getchar();
ch=ch+32;
putchar(ch);
putchar('\n');
printf("%c,%d\n",ch,ch);
//以两种格式输出
return 0;
}
getchar()使用行缓冲区存储输入的内容 (行缓冲区是一行的缓存)区,把控制台输入内容全部读入
4.用%c输入数据存在的问题
(1)增加一个getchar(),读掉输入的回车
(2)加一个空格
#include <stdio.h>
int main()
{
int data1,data2,sum;
char op;
scanf("%d %c %d",&data1,&op,&data2);
printf("%d%c%d=%d\n",data1,op,data2,data1+data2);
return 0;
}
四、循环分支
1.关系运算符和逻辑运算符
2.条件语句
条件表达式:
a>b?a:b;
3.开关语句
4.数值溢出和精度损失问题
5.软件测试与错误分析实例
程序测试:给定一组输入,通过运行被测程序,检查程序输出是否与预期结果一致。(测试用例)
测试过程就像黑客攻击的过程。
黑盒测试举例:写好了一个判断三角形类型的函数:用各类三角形的值输入。
6.程序=算法+数据结构
算法:对在数据上的操作的描述,不同的算法可能有不同的时间或空间效率。
数据结构:定义待操作的数据在计算机内存中是如何存储和组织的,选择恰当的数据结构可以提高程序的运行或存储效率。
算法的特性:有穷性、确定性、允许没有输入或者多个输入、必须有输出。
顺序结构、选择结构、
raptor软件:用于画流程图来运行程序的软件。
c语言生成随机数
#include <stdio.h>
#include <time.h>
int main()
{
srand(time(NULL));
//设置随机数种子为时间,不设置时默认为1
for(int i=0;i<5;i++)
{
printf("%d ",rand()%10+1);
}
printf("\n");
return 0;
}
五、循环
1.计数控制的循环
2.递推
正向递推与反向递推
3.条件控制的循环
4.嵌套循环
5.穷举
goto END;//无条件转向语句
END: ; //标号(标识符)后面必须有语句,哪怕是空语句
#include <stdio.h>
int main()
{
int x;
for(x=1;;x++){
if(x%5==1&&x%6==5&&x%7==4&&x%11==10){
printf("%d",x);
goto END;
}
}
END:;
return 0;
}
break与exit(0)--<stdlib.h>与使用标志变量tag(可读性更好)
尽量避免使用goto语句,在跳出多重循环和跳向共同的出口位置。
穷举法也叫枚举法:密码的破译、暴力搜索法
6.软件调试与错误实例分析
程序中常见的出错原因:
(1)编译错误:语法错误
(2)链接错误:缺少包含文件、或者包含文件的路径错误等
(3)运行时错误:运行结果与预期不一致程序无法正常运行
调试一个错误的程序能够极大提高我们的调试能力。
7.循环结构
循环结构的两种实现方法:
(1)当型循环:先检票后上车
(2)直到型循环:先上车后检票
六、函数:分而治之
1.函数的定义、调用、参数传递和函数原型
(1)标准库函数
ISO C定义的标准库函数
第三方库函数
(2)用户自定义函数
函数的定义:返回值类型、形式参数、return返回值(函数的出口)
函数原型: 就是函数先声明 --在函数开头定义函数的原型是一种良好的编程习惯
2.函数封装与程序的健壮性
(防御性程序设计)程序的健壮性:对非法输入进行判断
函数设计的基本原则
*检查入口参数的有效性、合法性
*检查函数调用成功与否
(1)函数规模要小
(2)函数功能要单一
(3)函数接口定义要清楚
3.断言与防御性编程
断言:assert(只在debug版本中有用,在relase版本中不起作用)
仅用于调试程序,不能作为程序的功能。
何时适合使用断言?
检查程序中的各种假设的正确性
证实或测试某种不可能发生的状况确实不会发生
使用断言的基本原则
使用断言捕获不应该或者不可能发生的情况
每个assert只检验一个条件
防御性编程
养成良好的编程风格
避免闪电式编程,用怀疑的眼光审视
简单就是一种美,不要滥用技巧,让你的代码过于复杂
编译时打开所有警告,不要忽略它们
使用安全的数据结构和函数调用
做内存的“好管家”
4.代码风格
花括号不跟在函数后面,另起一行写
变量名:小写字母开头,“名词”或者“形容词+名词”
oldValue , newValue
函数名:大写字母开头,“动词”或者“动词+名词”
GetValue() , SetValue()
宏和const常量全用大写字母,并用下划线分割单词
#define ARRAY_LEN 10
const int MAX_LEN=100;
5.自顶向下、逐步求精的结构化程序设计方法
七.递归
1.从嵌套调用到递归调用
递归调用:直接或间接调用自己。
2.递归是如何执行的
汉诺塔(Hanoi)问题:
#include <stdio.h>
void Hanoi(int n,char a,char b,char c);
void Move(int n,char a,char b);
int main()
{
int n;
printf("Input the number of disks:");
scanf("%d",&n);
printf("steps of moving %d disks from A to B by means of C:\n",n);
Hanoi(n,'A','B','C');
return 0;
}
void Hanoi(int n,char a,char b,char c)
{
if(n==1)
{
Move(n,a,b);
}
else
{
Hanoi(n-1,a,c,b);
Move(n,a,b);
Hanoi(n-1,c,b,a);
}
}
void Move(int n,char a,char b)
{
printf("Move %d:from %c to %c\n",n,a,b);
}
拓展:递归调用时的函数调用栈。
递归方法的优缺点:
优点:
简单、直观、精炼,易编、易懂、逻辑清晰、可读性好,更符合人的思维习惯,逼近数学公式的表示。
缺点:
函数调用开销大,耗费更多的时间和栈空间,时空效率偏低。
易产生大量的重复计算。
3.尾递归是什么?(与普通递归的区别)
在递归返回阶段直接返回给主函数。
4.变量的作用域
局部变量与全局变量
5.变量的生存期
声明变量的存储类型: 存储类型 数据类型 变量名;
C存储类型关键字
auto(自动变量) //离开函数,值就消失
static(静态变量) //离开函数,值仍保留
extern(外部变量)
register(寄存器变量) //频繁访问的变量放到寄存器中,无需我们特别声明
6.分治与迭代
分治:分而治之
迭代:
7.多文件结构
在本目录中新建一个.c文件就可以了
八、一堆数据
1.数组Array的定义和初始化
保存大量同类型数据
为什么数组下标从0开始?
--使编译器的实现简化一点,且下标的运算速度少量提高。
不能用变量n声明数组,最好用宏定义
int a[5]={62,74,56,88,90};
int a[5]={62,74};
int a[ ]={62,74,56,88,90};
一维数组元素的赋值
二维数组第二位不能省略
2.向函数传递数组
数组作为函数参数(call by value)与简单变量作为函数参数(call by reference)的区别
**传递的是形参数组的首地址
#include <stdio.h>
#define N 40
int Average(int score[],int n);
void ReadScore(int score[],int n);
int main()
{
int score[N],aver,n;
printf("Input n:");
scanf("%d",&n);
ReadScore(score,n);
//传入数组的第一个索引
aver=Average(score,n);
printf("Average score is %d\n",aver);
return 0;
}
void ReadScore(int score[],int n)
{
int i;
printf("Input score:");
for(i=0;i<n;i++)
{
scanf("%d",&score[i]);
}
}
int Average(int score[],int n)
{
int i,sum=0;
for(i=0;i<n;i++)
{
sum+=score[i];
}
return sum/n;
}
#include <stdio.h>
#define N 40
int Average(int score[],int n);
int ReadScore(int score[]);
int main()
{
int score[N],aver,n;
n=ReadScore(score);
//传入数组的第一个索引
printf("Total students are %d\n",n);
aver=Average(score,n);
printf("Average score is %d\n",aver);
return 0;
}
int ReadScore(int score[])
{
int i=-1;
do{
i++;
printf("Input score:");
scanf("%d",&score[i]);
}while(score[i]>=0);
//输入负数结束
return i;
}
int Average(int score[],int n)
{
int i,sum=0;
for(i=0;i<n;i++)
{
sum+=score[i];
}
return sum/n;
}
3.查找算法
(1)查找最大值
(2)线性查找LinSearch
(3)二分查找Binary Search(折半查找)
#include <stdio.h>
#define N 40
int ReadScore(long num[],int score[]);
int BinSearch(long num[],long x,int n);
int main()
{
int score[N],n,pos;
long num[N],x;
n=ReadScore(num,score);
//传入数组的第一个索引
printf("Input the searching ID:");
scanf("%ld",&x);
pos=BinSearch(num,x,n);
if(pos!=-1)
{
printf("score=%d\n",score[pos]);
}
else
{
printf("Not found!\n");
}
return 0;
}
int ReadScore(long num[],int score[])
{
int i=-1;
do{
i++;
printf("Input num,score:");
scanf("%ld%d",&num[i],&score[i]);
}while(score[i]>=0);
//输入负数结束
return i;
}
int BinSearch(long num[],long x,int n)
{
int low=0,high=n-1,mid;
while(low<=high)
{
mid=(high+low)/2;
if(x>num[mid])
{
low=mid+1;
}
else if(x<num[mid])
{
high=mid-1;
}
else
{
return mid;
}
}
return -1;
}
4.排序算法
(1)冒泡法排序 BubbleSort 外层n-1,内层n-i-1
#include <stdio.h>
#define N 40
int ReadScore(int score[]);
void BubbleSort(int score[],int n);
int main()
{
int score[N],n,pos;
long num[N],x;
n=ReadScore(score);
//传入数组的第一个索引
BubbleSort(score,n);
for(int i=0;i<n;i++)
{
printf("%d",score[i]);
}
return 0;
}
void BubbleSort(int score[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=1;j<n-i;j++)
{
if(score[j]<score[j-1])
{
temp=score[j];
score[j]=score[j-1];
score[j-1]=temp;
}
}
}
}
int ReadScore(int score[])
{
int i=-1;
do{
i++;
printf("Input score:");
scanf("%d",&score[i]);
}while(score[i]>=0);
//输入负数结束
return i;
}
(2)交换排序 ChangeSort
#include <stdio.h>
#define N 40
int ReadScore(int score[]);
void ChangeSort(int score[],int n);
int main()
{
int score[N],n,pos;
long num[N],x;
n=ReadScore(score);
//传入数组的第一个索引
ChangeSort(score,n);
for(int i=0;i<n;i++)
{
printf("%d",score[i]);
}
return 0;
}
int ReadScore(int score[])
{
int i=-1;
do{
i++;
printf("Input score:");
scanf("%d",&score[i]);
}while(score[i]>=0);
//输入负数结束
return i;
}
void ChangeSort(int score[],int n)
{
int i,j,temp;
for(i=0;i<n-1;i++)
{
for(j=i+1;j<n;j++)
{
if(score[j]<score[i])
{
temp=score[j];
score[j]=score[i];
score[i]=temp;
}
}
}
}
(3)选择法排序
#include <stdio.h>
#define N 40
int ReadScore(int score[]);
void SelectionSort(int score[],int n);
int main()
{
int score[N],n,pos;
long num[N],x;
n=ReadScore(score);
//传入数组的第一个索引
SelectionSort(score,n);
//选择排序
for(int i=0;i<n;i++)
{
printf("%d",score[i]);
}
return 0;
}
int ReadScore(int score[])
{
int i=-1;
do{
i++;
printf("Input score:");
scanf("%d",&score[i]);
}while(score[i]>=0);
//输入负数结束
return i;
}
void SelectionSort(int score[],int n)
{
int i,j,k,temp;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<n;j++)
{
if(score[j]<score[k])
{
k=j;
}
}
if(k!=i)
{
temp=score[k];
score[k]=score[i];
score[i]=temp;
}
}
}
九、指针
1.指针变量的定义、初始化及其解引用
NULL是空指针,无效指针
声明指针时最好先赋值,如果不知道指向谁,则设置为NULL
2.指针变量作为函数参数
(1)参数传递方式
(2)典型实例--两数交换
3.函数指针及其应用
(1)什么是函数指针?
(2)函数指针的典型应用--通用的排序函数
(3)函数指针的典型应用--计算任意函数的定积分
4.数组的趣味应用
(1)筛法求素数
(2)文曲星猜数字游戏
(3)螺旋矩阵
十、字符串
1.字符串的存储与表示
(1)字符串的输入和输出
(2)字符串的表示与存储
2.字符串处理操作
3.向函数传递和从函数返回字符串
(1)向函数传递字符串
(2)从函数返回字符串
4.缓存区溢出与缓存区溢出攻击
如下:存在缓冲区溢出的代码
#include <stdio.h>
#include <string.h>
int main()
{
char password[8]="secret",input[8];
while(1)
{
printf("Enter your password:");
gets(input);
if(strcmp(input,password)==0)
{
printf("Welcome!\n");
break;
}
else
{
printf("Sorry!\n");
}
}
return 0;
}
安全的输入更改:
十一、指针的孪生兄弟
1.指针的算术运算
2、指针和一维数组间的关系
3、指针和二维数组间的关系
4.指针数组及其应用
(1)用指针数组表示多个字符串
(2)用指针数组表示命令行参数
十二--结构体
1.结构体类型
2、结构体与数组的嵌套
3、结构体的相关计算和操作
(1)结构体所占内存的字节数
(2)对结构体的操作
4.向参数传递一堆不同类型的数据
(1)结构体指针
(2)向函数传递结构体
5.枚举类型和共用体类型有什么不同
(1)枚举类型
(2)共用体类型
6.典型应用实例--洗发牌模拟
十三、内存
1.何为动态内存分配
(1)C程序的内存镜像
(2)动态内存分配函数
2.动态数组
(1)动态数组的特点及基本操作
(2)动态数组的创建
(3)动态数组的增长和释放
3.常见的内存错误及其解决对策
十四、结构设计的艺术
1.单向链表
(1)基本概念
2.其他数据结构简介
十五、学会保存数据(文件操作)
1.二进制文件与文本文件
2.文件的打开和关闭
3.格式化数据的文件读写
4.字符和字符串的文件读写
(1)按字符读写文件
(2)fgetc(),fputc(),feof()的程序实例
fputc()向文本中写文件
fgetc()从文本中读取文件,EFO为结束标识符
feof()判读是否到了文件尾
(3)feof()函数在应用中存在的问题原因分析
(4)按行读写文件
5.内存数据块的文件读写
6.随机读写与文件缓存