本文主要是介绍C语言笔记,发上来保存一下,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
c语言
解释和编译;
解释 :就是一边翻译一边运行; 编译就是,先进行预处理,编译,汇编,连接,最后执行
百分号
%p输出一个指针;%u输出一个无符号十进制整数;%d有符号十进制整数;%lu无符号长整型整数;
%e按指数形式输出浮点数;%g按合适的方式输出浮点数;%f对应float类型;%lf对应double类型;
suchas:
#include<stdio.h>
intmain(intargc,charconst*argv[])
{
inta=0;
scanf("%d",&a);
printf("%d",a);
doubleaa;
scanf("%lf",&aa);
printf("%lf",aa);
}
寄存器
寄存器的功能是存储[二进制代码,它是由具有存储功能的触发器组合起来构成的。一个触发器可以存储1位二进制代码,故存放n位二进制代码的寄存器,需用n个触发器来构成
颜色
1-f分别代表的颜色如下:
0 = 黑色 8 = 灰色 1 = 蓝色 9 = 淡蓝色 2 = 绿色 A = 淡绿色 3 = 湖蓝色 B = 淡浅绿色 4 = 红色 C = 淡红色 5 = 紫色 D = 淡紫色 6 = 黄色 E = 淡黄色 7 = 白色 F = 亮白色
根号函数
#include<math.h>
sqrt(a) //根号
生成exe文件时,结尾加system("pause");,暂停;
幂函数
pow(a,b);
一些入门函数
#include<stdio.h>
intmain() // int main 或 void main()
{
return0;
} //基本框架
入门时第一个函数:printf
#include<stdio.h> //或者#include"stdio.h" ; 这两个的区别后面再说
intmian() //函数开始,主函数;一个C语言必须要有且只能有一个;程序从这里开始指行;【从c的层面,在这里开始执行】
{printf("123123"); //括号内的是输出内容
return0; //返回值,检查程序是否正常执行,若为叶程序则有向主程序提供参数等;
}
此函数结果是在终端上输出123123
第二个程序
#include<stdio.h>
intmain()
{intx; //int 为整形数据,定义变量x为整形;可以理解为整数;有大小上下限;寄存器大小决定;寄存器
scanf("%d",&x); //%d,为整形格式,定义输入时x为整形,&为提供寄存器存储其;scanf为输入函数;
printf("%d\n",x); // \n为换行符,若输入到scanf函数中将需要多输入一个字符用来填充空位;
return0;
}
第三个程序
#include<stdio.h>
intmax(intx,inty)
intmain()
{
intmax(intx,inty); //声明函数
intx=0,y=0,z=0; //定义变量
scanf("%d %d",&x,&y); //输入下x,y,并给与存储地址
z=max(x,y); //调用函数
printf("max=%d\n",z); //将最大值输出在屏幕上
return0;
}
intmax(intx,inty) //定义max函数,函数值为整型,形式参数为整型
{
intz; //max函数中的声明部分,定义z为整型;
if(x>y) z=x; //如果x>y,则z=x
elsez=y; //否则z=y
return (z); //将z的值反回,通过max返回调用处
}
第四个程序·case/switch 语句
#include<stdio.h>
intmain()
{inta=0; //声明参数a为整型,并初始化赋值为0;
scanf("%d",&a); //输入语句
switch(a){ //switch判断按照括号内的内容进行判断,此程序用a的值进行判断
case1:printf("你输入的是一");break; //case后为条件当a=1时 则执行case=1后的语句,break为结束语句如果没有则会向下指行
case2:printf("你输入的是二");break; //如果输入a=4;且4后面无break则会执行printf("你输入的是四");和printf("你输入的是五
case3:printf("你输入的是三");break; //")
case4:printf("你输入的是四");break;
case5:printf("你输入的是五");break;
default : //如果没有结果就执行这里
}
return0;
}
break是跳出循环
第五个程序 while语句
#include<stdio.h>
intmain ()
{inta=0,x=0; //先考虑要使用几个变量
printf("请输入一个正整数:");
scanf("%d",&a); //需要输入的变量(a为输入的正整数)
while(a>=1) //括号内的为判断条件,若满足该条件则进入循环一次,然后再判断是否继续循环
{a/=10;x+=1;} //若满足条件则进行该{}内的语句;"a/=10"为"a=a/10";如x+=1为x=x+1;
printf("你输入的是一个%d位数\n",x); //输出运行的结果
return0;
}
第六个程序 for语句
for循环语句:for(a=10;a>0;a--)
{循环体} //可解释为 对于一开始的a=10;当a>0时重复做循环体,使每一轮循环结束后使得a--;也就是说a-- 是在循环结尾处使用顺序如下初始化a=10.判断a是否大于0;执行循环体。再执行a--,然后再此重复
#include<stdio.h>
intmain()
{
inta=1,i=1,n=1;
printf("请输入一个正整数:");
scanf("%d",&a);
for(i=1;i<=a;i++) //( i=1是初始化 ,i<=a为判断条件,第一次进入循环时, )
{
n*=i;
} //进行循环
printf("%d!=%d\n",a,n);
return0;
}
for(初始动作;条件;每轮动作); for语句中每一个表达式都是可以省略的for(;条件;)==while(条件)
分号不能省略
for和while转换
#include<stdio.h>
intmain()
{
intn;
scanf("%d",&n);
intfact=1;
inti=1;
while(i<=n) //for(i=1;i<=n;i++){
{
fact*=i;i++;
} //fact*=i;}
printf("%d的阶乘为%d",n,fact);
return0;
}
补一个while的写法(do while)
#include<stdio.h>
intmain()
{ inta=0,n=0;
printf("请输入一个正整数:");
scanf("%d",&a);
do{ //先执行一次再判断条件,
a/=10;
n+=1;
}while(a>=1); //若满足括号内条件则进行循环
printf("你输入的是一个%d位数\n",n);
rerturn0;
}
bool类型
(#include<stdbool.h>)
#include<stdio.h>
#include<stdbool.h>
intmain()
{
boolb=6>5; //
printf("%d",b); //结果为1,因为6>5成立,所以b=1;
return0;
}
布尔类型是判断真假的数据类型;
逻辑运算
在c99中 iso646.h头文件中 用and代替&&;用or代替||,用not代替!;
逻辑运算是对逻辑量进行的运算,结果只有0或1;
逻辑量是关系运算或逻辑运算的结果
运算符 | 描述 | 示例 | 结果 |
! | 逻辑非 | !a | 如果a是true结果就是false;如果a是false则结果就是true;取反 |
&& | 逻辑与 | a&&b | 如果a和b都是true结果就是true;否则就是false:同1则1; |
|| | 逻辑或 | a||b | 如果a和b有一个是true,结果为true;两个都为false结果为false,有1则1; |
表达数学区间如x为(4,6) 则应写为x>4&&x<6;
判断一个字符是否为大写字符 c>=‘A’&&c<=‘Z’
! age<20(!为单目优先级高)
逻辑运算是从左向右进行的如果左边的结果已经能决定结果,就不会做右边的运算;也就是说对于&&来说左边是false时就不做右边了,对于||来说左边是true时就不做右边了
条件运算符
* 运算符 : 用来取出它右边那个指针所指的变量;
· ?是一个三元运算符;
count = (count>20) ? count-10 : count+10; //用if表示
if(count>20)
count-=10;
else count+=10;
逗号运算符 逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果,逗号的运算优先级是最低的;逗号的组合关系是自左向右,所以左边的运算会先计算,而右边的表达式的值留下来作为逗号运算的结果。
#include<stdio.h>
int main()
{int a,b;
a=3+4,5+6; //a=7
b=(3+4,5+6); //b=11括号
printf("%d %d",a,b);
return 0;
} //主要是再for中使用,目前来说逗号表达式只有这一个用处;
C语言运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式)/函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 前置自增运算符 | ++变量名 | 单目运算符 | ||
++ | 后置自增运算符 | 变量名++ | 单目运算符 | ||
-- | 前置自减运算符 | --变量名 | 单目运算符 | ||
-- | 后置自减运算符 | 变量名-- | 单目运算符 [4] | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式/整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | 左移 | 变量 | 左到右 | 双目运算符 | |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
小于 | 表达式 | 双目运算符 | |||
小于等于 | 表达式 | 双目运算符 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
左移后赋值 | 变量 | ||||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | 从左向右顺序运算 |
说明:
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
递归调用
/*在调用一个函数的过程中又出现直接或者间接的调用该函数本身,称为函数的递归调用(c语言的特点之一就是允许函数的递归调用) 例如:*/
#include<stdio.h>
int f(int x)
{
int y,z;
z=f(y)
return (2*z);
}
//这种情况的调用是无休止的(还有间接调用)
//如:两个函数构成死循环
int max(int x,int y)
{
int m=m(x,y);
z=x*y;
return (z-m);
}
int m(int a,int b)
{
int c=max(a,b);
return c;
}
//显然,这两种递归调用都是无休止的,而程序不应该出现这种无休止的递归调用;而应出现有限次数的、有终止的递归调用,这可用if语句来控制,只有在某一条件时才继续执行递归调用,否则便不再继续。
补:getchar()和putchar
#include <stdio.h>
int main ()
{
char c;
printf("请输入字符:");
c = getchar(); //读取键盘输入的内容,辅助给c
printf("输入的字符:");
putchar(c); //打印c
if((c = getcahr()) = 1 )
{
...;
}
return(0);
}
可能从这里开始是算法
第个七程序 三个数字取最大
#include<stdio.h>
intmain()
{inta,b,c,max;
printf("请输入三个正整数:");
scanf("%d %d %d",&a,&b,&c);
if(a>b)
{
if(a>c)max=a;
elsemax=c;}
else{if(b>c) max=b;
elsemax=c;
}
printf("%d\n",max);
return0;
}
一个技巧
可以判断它输出了几个来看它循环了几轮
while(a>0){
a--;printf("%d",a);
}
第八个程序 求平均数
#include<stdio.h>
intmain()
{
intsum=0; //sum用来储存输入数字的和
intnumber=0; //number为输入的数字
inta=0; // a用来储存有几位数字
scanf("%f",&number); //%f浮点数,
while(number!=-1) // number=-1时结束循环 !是逻辑非
{sum+=number; // 将输入的数字之和放入number
a++; //每次输入数字后加一;此为输入数字的个数
scanf("%f",&number); //%f为浮点数格式如同上一个解释
}
printf("%f\n",sum/a); //%f表明输出格式,sum中储存着数字之和;a为数字个数;
return0;
}
随机数
#include<stdio.h>
#include<sdlib.h>
#include<time.h>
intmain(){
srand(time(0));
inta=rand();
printf("%d\n",a);
return0; //该程序是为了提供一个伪随机数
}
第九个程序 猜数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{srand(time(0));
int number=rand()%100+1; //令number为随机数且取余 因为仅取两位可能是0可能是99,无法表示100,则取余后加一
int count=0;
int a=0;
printf("已随机生成一个1到100内的数字。");
do{ //do while循环
printf("请猜这个1到100之间的数:");
scanf("%d",&a); //输入数字
count++; //记录输入几次,即猜了几次
if(a>number) //如果a>number则输出大于;
{printf("你猜的数大于随机数");} //
else if(a<number) //否则如果a<number则输出小于
{printf("你输入的数字小于随机数");} //
}while(a!=number); //结束a!=number的意思是a不等于number;
printf("恭喜你猜对了,答案是%d。\n你用了%d次猜对答案",number,count);
return 0;
}
第十个程序逆序取数
#include<stdio.h>
int main()
{
int x=0;
scanf("%d",&x);
int digit;
int ret=0;
while(x>=1){
digit=x%10;
x/=10;
ret=ret*10+digit;
printf("原数此时为%d\n,逆序此时为%d\n",x,ret,);
}
printf("逆取数最终为%d\n",ret);
return 0;
}
第二个·按位取逆
#include<stdio.h>
int main()
{int x=0;int digit,n=0;
printf("请输入一个正整数:\n");
scanf("%d",&x);
printf("该数原数为%d\n",x);
printf("该数取逆为:");
do
{digit=x%10;
printf("%d",digit);
n++;
x/=10;
}while(x>0);
printf("\n该数是%d位数,",n);
return 0;
}
后来的我,懒的打注射
第十一个判断素数
#include<stdio.h>
int main()
{int x=0;
int a=1;
int i=1;
printf("请输入一个正整数:");
scanf("%d",&x);
for(i=2;i<x;i++){ //for初始为2,因为1和任何数字取余都是0;
if(x%i==0){a=0; break; //x%i==0,如果x对于i取余为0;则条件成立;注意如果把==误打为=;则条件判断变为赋值;
} //break是当进入循环时中断出来
}
if(a==1) { //再次强调为==;这个才是等价;
printf("是素数");}
else {
printf("不是素数");
}
return 0;
}
continue:跳出循环这一轮
剩下的语句进入下一轮;
#include<stdio.h>
int main ()
{int x=0;
int a=1;
int i=1;
printf("输入数字");
scanf("%d",&x);
for(i=2;i<x;i++)
{
if(x%i==0)
{
a=0;
printf("1\n");
break;
printf("2\n"); //contnue是中断一轮循环进行下一轮;break是中断整循环
//for(i=2;i<x;i++){if(x%i==0){a=0;printf("1\n");continue;break;printf("2\n");}} 可以尝试换一下break和continue看看输出结果
}
printf("%d\n",i);
}
if(a==1){printf("是");}
else {printf("不是");
} return 0;
}
猜数改进
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{srand(time(0));
int ret=rand()%100+1;
int count=0,n=1;
printf("计算机已经随机生成一个1到100的数字\n");
printf("请您尝试猜一猜:");
scanf("%d",&count);
if(count>ret)
{printf("你输入的数字大于随机数,请您再猜一猜\a");}
else if(count<ret)
{printf("你输入的数字小于随机数,请您再猜一猜\a");}
else if(count=ret)
{printf("\n欧气满满,您一次就猜对了\a");}
while(count!=ret)
{
scanf("%d",&count);
if(count>ret)
{printf("你输入的数字大于随机数,请您再猜一猜\a");}
else if(count<ret)
{printf("你输入的数字小于随机数,请您再猜一猜\a");}
n++;
}
printf("\n你输入了%d次得出答案\a",n);
printf("\n恭喜您猜对了随机数为%d\a",ret);
printf("\n祝您游戏愉快\a");
return 0;
}
判断1到100内素数
设计思路:从1到100中循环一个一个输入循环,使用for循环,需要一个变量;二.使用判断素数,1和0;一个变量递增;
进入循环后第一步定义变量,然后1到100的表示(for);然后每次判断的循环if,输出结果printf;
#include<stdio.h>
int main ()
{
int x,number,i=1; //number为1到100,x为判断1或者0;i为用来做循环;
for(number=2;number<=100;number++)
{
for(i=2;i<number;i++){if(number%i==0){x=0;break;}
else {x=1;}
if(a==1) {printf("是素数的:"); printf("%d\n",number);
number++;
}
return 0;
}
第十二个程序 猜硬币(三重循环)
#include<stdio.h>
int main()
{
int x;
int one,two,five;
scanf("%d",&x);
for(one=1;one<x*10;one++){
for(two=1;two<x*10/2;two++){
for(five=1;five<x*10/5;five++){
if(one+two*2+five*5== x*10){
printf("可以用%d个一角加%d个两角加%d个五角得到%d元",one,two,five,x);}
}
}
}
return 0;
}
*按位取逆改进
//按位取逆从一开始到x的全部输出取逆,思路:
//一.构思二个块(1.按位取逆块 2.逐步递加块); 先二后一;
//二设置变量(根据要求1.循环的count,输入的x;保存位数的n,保存逆取的a;又好像两个循环中条件最好不是同一个参数;所以还有一个b;)
#include<stdio.h>
int main()
{int count=0,x=0,n=0,a=0,b=0; //x为输入数,n为位数,a为存逆数,b为循环中另一个参数;count为设置的截止数;
printf("请输入截止数字:");
scanf("%d",&count);
for(x=1;x<=count;x++){
printf("\n该数原数为:%d",x); //输出每次加一后的原数;
printf("\n该数取逆为:");
b=x; //b=x把x的值赋予b;因为如果以x再进行内嵌的循环;在循环中x的值会被改变;无法输 n=0; //正确的结果; n=0;每次内循环完成后重置n的值,以供接下来的循环使用;
do
{
a=b%10;
printf("%d",a);
b/=10;
n++;
} while(b>0);
printf("\n该数是%d位数",n);
}
return 0;
}
接力break
#include<stdio.h>
int main()
{
int x;
int one,two,five;
int exit=0;
scanf("%d",&x);
for(one=1;one<x*10;one++){
for(two=1;two<x*10/2;two++){
for(five=1;five<x*10/5;five++){
if(one+two*2+five*5== x*10){
printf("可以用%d个一角加%d个两角加%d个五角得到%d元\n",one,two,five,x);exit=1;break;
}
}if(exit==1)break;
}if(exit==1)break;
}
return 0;
}
goto 使用
//goto在多重循环中使用,可以从最内层跳到最外层;后跟标签,跳出位置以标签位置后接:号
#include<stdio.h>
int main()
{
int x;
int one,two,five;
int exit=0;
scanf("%d",&x);
for(one=1;one<x*10;one++){
for(two=1;two<x*10/2;two++){
for(five=1;five<x*10/5;five++){
if(one+two*2+five*5== x*10){
printf("可以用%d个一角加%d个两角加%d个五角得到%d元\n",one,two,five,x);exit=1;goto a;
}
}
}
} a:
return 0;
}
简单的练习
#include<stdio.h>
int main()
{printf("f(n)=1/1+1/2+1/3....+1/n\n");
long long int n;
long long int i;
double sum=1.0;
printf("求f(n)\n");
printf("请输入n的数值:");
scanf("%d",&n);
for(i=1;i<=n;i++){
sum+=1.0/i;
}
printf("f(%d)=%f",n,sum);
}
求两数的最大公约数
枚举法
#include<stdio.h>
int main ()
{int a,b,ret,min,i;
scanf("%d %d",&a,&b);
if(a>b){
min=b;
}else {
min=a;
}
for(i=1;i<min;i++){
if(a%i==0){
if(b%i==0){
ret=i;
}
}
}printf("%d和%d的最大公约数为%d",a,b,ret);
return 0;
}
第二种方法 辗转相除法(需学习数学原理)
#include<stdio.h>
int mian()
{int a=1,b=1,t=1;
scanf("%d %d",&a,&b);
while(b!=0){
t=a%b;
a=b;
b=t;
}
printf("最大公约数为%d",a);
return 0;
}
数组(a[ i ] = *(a+i))
数组 : 数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。
数组是从0开始记下标 number[0]是第一个数组元素
#include<stdio.h>
int main ()
{
int cut=0;
double sum=0;
int number[100]; //定义数组,数组名称为number,数组中可容纳元素为99个,c语言中最后一位用来标示结尾;
int x;
scanf("%d",&x);
while(x!=-1){
number[cut]=x; //[]中为下标,如number[1],此处为其赋值;
sum+=x;
cut++;
scanf("%d",&x);
}
if(cut>0){
printf("%f\n",sum/cut);
int i;
for(i=0;i<cut;i++){
if(number[i]>sum/cut){
printf("%d\n",number[i]);
}
}
}
return 0;
}
自定义函数
1.声明 2.调用 3.定义
当然 也可以直接在声明时直接定义;
#include<stdio.h>
void sum(); //函数声明 记得加分号
int main()
{ printf("请输入两个整数,我会为你计算出其中间整数之和");
sum(); //函数调用
return 0;
}
void sum() //函数定义
{int i,num=0,a,b;
scanf("%d %d",&a,&b);
if(b>a)
{
for(i=a;i<=b;i++){num+=i;}
printf("该两数之间和为%d",num);
}
else if(b>a)
{
for(i=b;i<+a;i++){num+=i;}
printf("该两数之间和为%d",num);
}
else if(a==b)
{
printf("无法计算哦");
}
}
函数传参
#include<stdio.h>
void f(int i)
{
printf("%d",i);
}
int main()
{
f(2.5);
return 0;
}
参数交换
每个函数都有自己的变量空间参数也位于这个独立的空间中1,与其他参数没有关系;
#include<stdio.h>
voidswap(inta,intb); //函数声明,此处是形参
intmain()
{ inta=5,b=6;
swap(5,6); //函数调用,这里是实参;
printf(" %d %d",a,b);
return0;
}
voidswap(inta,intb) //函数定义。这里是形参
{
intt=a;
a=b;
b=t;
printf("函数中%d %d\n",a,b);
}
//在该函数中,结果输出时,在自定义函数中的值发生交换;在主函数中不发生交换;
本地变量
函数每次运行都会产生一个独立的变量空间;在这个空间中的变量,是函数运行所独有的称作本地变量;
定义在函数中的变量就是本地变量;
参数也是本地变量;
变量的生存期和作用域
生存期:什么时候这个变量开始出现,到什么时候它消亡;
作用域:在代码的什么范围内可以访问这个变量(也就是这个变量在什么范围内起作用);
对于本地变量来说这个两个答案是统一的:即大括号内----块;
#include<stdio.h>
intmain()
{
inta;
scanf("%d",&a);
printf("%d",a);
if(a>1){
intb=10; //b的作用域是在if语句内;
printf("%d",b);
}
return0;
}
本地变量的规则
本地变量是定义在块内的;
它可以是定义在函数的块内;
也可以定义在语句的块内;
甚至可以随便拉一对大括号来定义变量;
程序运行进入这个块之前,其中的变量不存在,离开这个块,其中的变量就消失了;
但是块外面定义的变量在里面仍然有效;
块里面定义的变量如果和外面一样,则会在块内张暂时覆盖其变量;
不能在同一个块内声明两个同名的变量;
本地变量不会被默认初始化;
参数在进入函数的时候就被初始化了;
#include<stdio.h>
int main()
{
int a=10;
{
int a=9;
printf("%d",a); //输出a为9;
}
printf("%d",a); //输出a为10;
return 0;
}
//在这个程序中二个a的值输出不一样;因为大括号把内外隔断;
没有参数时
void f(void); 还是void f();
在传统c语言中下,它表示f()函数中参数未知,并不表示没有参数;
c语言不允许函数中定义函数
也就是,不允许函数嵌套定义;
int a,b,sum(int x,int y); //是可以这样写的,我们说sum函数要两个变量作为参数,并返回一个整型参数,这样是可以的;
main是一个函数,return是返回给调用main的地方;
&符号 取得变量的地址,他的操作数必须是变量;
#include<stdio.h>
int main()
{
int a[]={1,2,3,4,5,6,7,8,9};
int r=sizeof(a)/sizeof(a[0]);
printf("%d",r);
return 0;
}
运行结果 r=9;
查位置
#include<stdio.h>
int ser(int key,int a[],int len) //自定义函数,定义了形参;
{
int ret=-1; //初始化;ret=-1;当数组中无相关数是输出-1;
int i;
for(i=0;i<len;i++) //在主函数中len代表是数组中1元素个数;
{
if(key==a[i])
{
ret=i; //如果key也就是被检查的数;等于a[i]时;ret=下标i;
break;
}
}
return ret; //如果成立时,返回的是小下标;不成立时返回的-1;
}
int main()
{
int a[]={1,2,3,4,5,6,7,8,9,10};
int g;
scanf("%d",g);
int r=ser(g,a,sizeof(a)/sizeof(a[0])); //g为被查数;a是数组名;sizeof一串就是len;
printf("%d",r);
return 0;
}
二维数组运算
内存中的二维数组
二维数组在概念上是二维的,有行和列,但在内存中所有的数组元素都是连续排列的,它们之间没有“缝隙”。以下面的二维数组 a 为例:
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
从概念上来讲:二维数组就像一个矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
但是在内存中它的分布是一维线性的;整个数组占用一块连续的内存:
1 2 3 4 5 6 7 8 9....
C语言中的二维数组是按行排列的,也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节,整个数组共占用 4×(3×4) = 48 个字节
假设数组 a 中第 0 个元素的地址为 1000,那么每个一维数组的首地址如下图所示:
为了更好的理解[指针]和二维数组的关系,我们先来定义一个指向 a 的指针变量 p:
int (*p)[4] = a;
括号中的*表明 p 是一个指针,它指向一个数组,数组的类型为int [4],这正是 a 所包含的每个一维数组的类型。
[ ]的优先级高于*,( )是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p 就成了一个指针数组,而不是二维数组指针。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p 指向的数据类型是int [4],那么p+1就前进 4×4 = 16 个字节,p-1就后退 16 个字节,这正好是数组 a 所包含的每个一维数组的长度。也就是说,p+1会使得指针指向二维数组的下一行,p-1会使得指针指向数组的上一行。
数组名 a 在表达式中也会被转换为和 p 等价的指针!
下面我们就来探索一下如何使用指针 p 来访问二维数组中的每个元素。按照上面的定义:
(1)p指向数组 a 的开头,也即第 0 行;p+1前进一行,指向第 1 行。
(2)*(p+1)表示取地址上的数据,也就是整个第 1 行数据。注意是一行数据,是多个数据,不是第 1 行中的第 0 个元素,下面的运行结果有力地证明了这一点:
#include <stdio.h>
int main(){
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a; //可以理解为它指的是a[0]这一行;
printf("%d\n", sizeof(*(p+1)));
return 0;
}
二分法
输入一个数字;然后在有序的数组中进行筛选;
每次用中间的数去比较、问题是判断条件;每次判断条件若大于则把left右移到maxid;若小于则把right左移到maxid;
#include<stdio.h>
intser(inta[],inti,intlen) //定义函数:a为数组;i和len;
{ intret=-1; //初始化ret被返回的值
intleft=0,right=len-1; //left是最左边的下标,从零开始;right是最由边的下标,不过因为下标从零开始,所以要减一;
while(left<right) //当
{
intmid=(left+right)/2;
if(a[mid]==i){
ret=mid;
break;
}
elseif(a[mid]>i){
right=mid-1;
}
else{left=mid+1;
}
}
returnret;
}
intmain()
{
inta[]={1,2,3,4,5,6,7,8,9,10};
intret=ser(a,5,sizeof(a)/sizeof(a[0]));
printf("下标为%d",ret);
return0;
}k
选择排序
将无序的数组,排成从小到大;
#include<stdio.h>
intmax(inta[],intlen) //数组a[],len是数组的数据个数 max函数作用是找到最大的数组的下标,并将其赋值给maxid
{
intmaxid=0; //用来遍历
inti=1;
for(;i<len;i++) //找出最大的数的下标
{
if(a[i]>a[maxid])
{maxid=i;}
}
returnmaxid;
}
intmain()
{
inta[]={12,15,66,78,5,96,48,46,44,30,456,986};
intlen=sizeof(a)/sizeof(a[0]);
//选择排序
inti=len-1; //减一:是循环完一次后最大的已经放到最后,该在剩下的里面找所以-1;
for(;i>0;i--)
{
intmaxid=max(a,i+1); //调用函数;max
//swap a[maxid],max[len-1]
intt=a[maxid];
a[maxid]=a[i];
a[i]=t;
}
intc=0;
for(;c<len;c++)
{
printf("%d",a[c]);
}
return0;
}
排序
思路:用一个数字去比较,先将最大的找到,放在最后一位;然后减1;继续用第一个去比较,此时去掉最大的数;继续去循环比较;
问题一:循环的条件;
问题二:循环中的元素的改变;
问题三:
#include<stdio.h>
int max(int a[],int len)
{
int maxid=0;
int i;
for(i=1;i<len;i++)
{
if(a[maxid]<a[i])
{
maxid=i;
}
}
return maxid;
}
int main()
{
int a[]={9,8,7,6,5,4,3,2,1,8486,123,45,778};
int len=sizeof(a)/sizeof(a[0]);
int maxid=max(a,len);
printf("%d",maxid);
return 0;
}
#include<stdio.h>
int max(int a[],int len)
{
int maxid=0;
int i;
for(i=1;i<len;i++)
{
if(a[maxid]<a[i])
{
maxid=i;
}
}
return maxid;
}
int main()
{
int a[]={9,8,7,6,5,4,3,2,1,8486,123,45,778};
int len=sizeof(a)/sizeof(a[0]);
int t,i;
for(i=len-1;i>0;i--)
{int maxid=max( a,i+1);
int t=a[maxid];
a[maxid]=a[i];
a[i]=t;
}//swap a[maxid]交换a[len-1];
int m;
for(m=0;m<len;m++)
{
printf("%d",a[m]);
}
return 0;
}
排序算法2
int i,j,t;
for(i = 0; i<len-1; ++i)
{
for(j = 0; j<len ; ++j)
{
if(a[i]>a[j])
{
t = a[i];
a[i] = a[j];
a[j] = t;
}
}
}
多维数组
a[ ] [ ]为二维数组;
a[ ] [ ] [ ] 为三维数组;
先放一下;
指针
1、实际上并不存在多维数组,所谓的多维数组本质上是用一维数组模拟的。
2、数组名是一个常量(意味着不允许对其进行赋值操作),其代表数组首元素的首地址。
3、数组与指针的关系是因为数组下标操作符[],比如,int a[3][2]相当于*(*(a+3)+2) 。
4、指针是一种变量,也具有类型,其占用内存空间大小和系统有关,一般32位系统下,sizeof(指针变量)=4。
5、指针可以进行加减算术运算,加减的基本单位是sizeof(指针所指向的数据类型)。
6、对数组的数组名进行取地址(&)操作,其类型为整个数组类型。
7、对数组的数组名进行sizeof运算符操作,其值为整个数组的大小(以字节为单位)。
8、数组作为函数形参时会退化为指针。
二维数组指针
括号中的*表明p是一个指针,它指向一个数组,数组的类型为int[4],这正是a所包含的每一个一维数组的类型。
[]的优先级高于*,()是必须要加的,如果赤裸裸地写作int *p[4],那么应该理解为int *(p[4]),p就成了一个指针数组,而不是二维数组指针。
对指针进行加法(减法)运算时,它前进(后退)的步长与它指向的数据类型有关,p指向的数据类型是int[4],那么p+1就前进4x4=16字节,p-1就后退16个字节,这正好是数组a所包含的每个一维数组的长度,也就是说,p+1会使得指向指向二维数组的下一行,p-1会使得指针指向数组的上一行。
数组名a在表达式中会被转换为和p等价的指针。
下面我们就来探索以下如何使用指针p来访问二维数组中的每个元素,按照上面的定义:
1 .p指向数组a的开头,也即第0行;p+1前进一行,指向第1行。
2. *(p+1)表示取地址上的数据,也就是整个第1行数据,注意是一行数据,是多个数据,不是第1行中的第0个元素,下面的运行结果有力地证明了这一点:
3)*(p+1)+1表示第1行第1个元素的地址。
*(p+1)单独使用时表示的时第1行数据,放在表达式中会被转换为第1行数据的首地址,也就是第1行第0个元素的地址,因为使用整行数据没有实际的含义,编译器遇到这种情况都会转换为指向该行第0个元素的指针;就像一维数组的名字,在定义时或者个sizeof, & 一起使用时才表示整个数组,出现在表达式中就会被转换为指向数组第0个元素的指针。
4)*(*(p+1)+1)表示第1行第1个元素的只,很明显,增加了一个*表示取地址上的数据。
根据上面的结论,可以很容易推出以下的等价关系:
a+i = p+i
a[i] == p[i] == *(a+i) == *(p+i)
a[i][j] == p[i][j] == *(a[i]+j) == *(p[i]+j) == *(*(a+i)+j) ==*(*(p+i)+j)
代码
#include<stdio.h>
intmain(){
inta [3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int*(p)[4] =a;
printf("%d\n",sizeof(*(p+1)));
return0;
}
//运行结果
16
使用指针遍历二维数组.
#include<stdio.h>
intmian(){
inta[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
int (*p)[4];
inti,j;
p=a;
for(i=0;i<3;i++){
for(j=0; j<4;j++) printf("%2d" ,*(*(p+i)+j));
printf("\n");
}
return0;
}
//运行结果
0 1 2 3
4 5 6 7
8 91011
总结:二维数组的指针指的是一行;当指针加一是就变成了下一行;定义是应该int (*p) [ 4 ] =
inta[3][4] = {{0,1,2,3},{4,5,6,7},{8,9,10,11}};
int (*p)[4] =a; //因为指的是一行所以一行有多少个就定义指针数组是多少
printf("%d",*(*p)); //输出当前的第一个指向的
printf("%d\n",*(*(p+1))); //输出下一行指向的 从a[0]-->a[1]
printf("%d\n",*(*(p)+1)); //输出下一个指向的
解引用???
指针基础
#include<stdio.h>
int main()
{
int a[] = {1,2,3};
int *p = a;
printf("%d\n",*p); //输出的是a(所指变量)的值;
printf("%d\n",p); //输出的是a(所指变量)的地址;
return 0;
}
sizeof 符号:计算字节长;字节(byte)=8位(bit);
#include<stdio.h>
int main()
{
int i=1,a;
a=sizeof(i);
printf("%ld",sizeof(int)); //%ld是长型整数,%d是有符号整型;
printf("%ld",sizeof(i));
printf("%ld",a);
return 0;
}
&(运算符):获得变量地址,必须是变量(只能是变量)
#include<stdio.h>
int main()
{
int i=1;
//printf("0x%x",&i);错误的 //%x是十六进制;
printf("%p",&i);
return 0;
}
取变量地址;
指针函数
#include<stdio.h>
int *f(void)
{
int i = 0;
return &i;
}
int main()
{
int *p = f()
return 0;
}
地址
数组的地址
#include<stdio.h>
int main()
{
int a[10];
printf("%p\n",a);
printf("%p\n",&a);
printf("%p\n",&a[0]);
printf("%p\n",&a[1]);
printf("%p\n",&a[2]);
/*
0000 0000 0062 FDF0;
0000 0000 0062 FDF0;
0000 0000 0062 FDF0;
0000 0000 0062 FDF4;
0000 0000 0062 FDF8;
*/
return 0;
}
#include<stdio.h>
int main()
{
int i;
int p;
printf("%p\n",&i);
printf("%p\n",&p);
return 0;
}
结果:
000000062FE1c--->i 地址(先输入的地址高)
000000062FE18--->p 地址(后输入的地址低)
相差4字节;
指针格式
#include<stdio.h>
int main()
{
int i;
int* p=&i; //p获得i的地址;这时我们说指针指向了i;
return 0;
}
1.int *p,q;
2.int* p,q;
3.int *p,*q;
1和2是一样的意思;*可以接近int可以接近P,但是只有P是指针;而q是一个变量;意思就是*是赋予的p,而不是int;
指针就是保存地址的变量;
*号也是一个单目运算符;
用来访问指针的值所表示的地址上的变量;
可以做右值也可以做左值;
int k=*p;
*p=k+1;
尝试了一下*p
#include<stdio.h>
void f(int *p)
{
printf("p=%p\n",p);
printf("*p=%d",*p);
}
int main()
{
int i=11;
printf("&i=%p",&i);
f(&i);
return 0;
}
输出结果:
&i=0000 0000 0062 FE1C
p=0000 0000 0062 FE1C
*p=11;
*p可以看做为整数;
数组变量是特殊的指针
//在形参中
//sum(int *a)与sum(int a[]);等价
//数组变量本身表示地址,所以
int a[10]; int *p=a;
//不用&取地址
//但是数组的单元表达的时变量,所以需要用&来取地址;
a == &a[0]
//[]运算符可以对数组做也可以对指针做:
p[0] <==> a[0]
// *运算符可以对指针做也可以对数组做;
指针的使用
因为自定义函数只能返回一个值;当我们想交换数字时;就可以用指针来自定义函数
void swap(int *pa,int *pb);
int main (void)
{
int a=1;
int b=2;
swap(&a,&b); //注意格式
printf("%d %d",a,b);
return 0;
}
void swap(int *pa,int *pb)
{
int t = *pa;
*pa = *pb;
*pb = t;
}
多级指针以及解引用
int a = 10;
int b = 20;
printf("%d %d\n",a,b); //10 20
int *p = &a; //p里保存a的地址
*p = 100; //对p解引用,将p指向的地址的内容赋为100
p = &b; //p指向b
*p = 200; //将p指向的地址的内容赋为200
printf("%d %d\n",a,b); // 100 200
int **pp = &p; //二级指针pp指向p
*pp = &a; //对pp解引用一次,即,使p指向a
**pp = 1000; //对pp两次解引用,第一次对pp解引用即p里面保存1000,
//第二次对p解引用即a里面保存1000
printf("指向a时p的内容:%d\n",*p); //1000
*pp = &b; //pp指向b
**pp = 2000; //第一次对pp解引用即p里面保存2000,
//第二次对p解引用即b里面保存2000
printf("指向b时p的内容:%d\n",*p); //2000
printf("%d %d\n",a,b); //1000 2000
int ***ppp=&pp;
**ppp=&b; //使p指向b
***ppp=20000; //对ppp解引用三次
printf("pp内容:%d p内容%d b=%d\n",**pp,*p,b); //20000 20000 2000
**ppp=&a; //使p指向b
***ppp=10000; //对ppp解引用三次
printf("pp内容:%d p内容%d a=%d\n",**pp,*p,a); //10000 10000 10000
return 0;
const指针和值(适用于C99)
const指针
b变量可以变,指针的地址不能变;
//表示一旦得到了某个变量的地址,不能在指向其他变量;
int *const q = &i;
//q是const
*q = 26;
//可以这么写,结果是i=26;
//q++则不行;
所指是const
//表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
const int *p = &i;
*p=26; //不行 (*p)是const
i = 26; //可以,i不是const
p = &j; //可以
//p不是const,p可以指向别人;i可以被改变值,但不能通过p去改变i的值;
判断那个被constl的标志是const在*的前面还是后面;
如果const在*的前面,表示说它所指的东西不能被修改;
如果const在*的后面,表示说指针不能被修改
转换
//总是可以把一个非const的值转换成const的
void f(const int *x);
int a = 15;
f(&a); //ok
const int b = a;
f(&b); //ok
b = a + 1; //no
//使用情况:当要传递的参数类型比地址大的时候,这是常用手段:既能用比较少的字节数传递值给参数,又能避免函数对外面的变量的修改,
const数组
const int a[] = {1,2,3,4,5,6};
//数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
//所以必须通过初始化进行赋值
保护数组值
//因为把数组传入函数是传递的是地址,所以那个函数内部可以修改数组的值
//为了保护数组不被函数破环,可以设置参数为const
int sum(const int a[],int length);
这样数组的值在函数中不会被改变
指针运算
char和int类型指针的不同
#include<stdio.h>
int mian (int argc,char const *argv[])
{
int a[]={0,4,16,32,64,128};
int *p=a;
char *pp=a;
printf("int p=%p\n",p); //设p=0X00000000
printf("int p+1=%p\n",p+1); //在int下p+1=0X00000004;int为4字节
printf("char P+1=%p\n",p+1); //char类型为一个字节;
return 0;
}
指针加一的用法
#include<stdio.h>
int main(int argc,char const *argv[])
{ int c[]={1,2,3};
int *n=c;
printf("c[1]=%d\n",c[1]);
printf("n=%p\n",n);
{
*(n+1)=15; //由于运算级的不同,所以先进行n+1;
printf("c[1]=%d",c[1]);
}
//或者
{
pintf("%d\n",*(p+1));
}
return 0;
}
//在int类型时加一就是4字节,可以用指针来操控下一个单元;
//char的类型则为加四;用来操控下一个单元;
给一个指针加一(这个一指的是一个 sizeof)表示要让指针指向下一个变量
int a[10];
int *p = a;
*(P+1)-->a[1];
//如果指针不是指向一片连续分配的空间;如数组,则这种运算没有意义;
当然可加就可减
指针减指针
#include<stdio.h>
int main ()
{
int a[] = {1,2,3,4,5,6,7,8,9};
int *p = a;
int *p1 = &a[8];
printf("p1-p = %d\n",p1-p);
return 0;
}
//结果时8;按地址大小去减是32;但是输出的结果是32/sizeof(int);也就是除于4(字节);也就是两个地址之间可以放8个int类型的单元
*p++
取出p所指的那个数据后顺便把p移到下一个位置去;
常用于数组类的连续操作
//++的运算优先级高于*;
{
int a[]={1,2,3,4,5,6,7,8,9};
int *p=a;
while()
{
printf("%d\n",*p++); //遍历数组;
}
}
指针比较
1.< , <=,==,>,>=,!=都可以对指针做;
2.比较它们在内存中的地址;
3.数组中的单元的地址肯定是线性递增的;
0地址
1.内存中有个0地址,但是0地址通常是一个不能随便碰的地址
2.所以你都指针你应该具有0值
3.因此可以用0地址来表示特殊的事情
返回的指针是无效的
指针没有被真正初始化(先初始化为0)
NULL是一个预定定义的符号,表示0地址
有的编译器不愿意你用0来表示0地址;所以一般用NULL;
指针的类型
1.无论指向什么类型,所有的指针的大小都是一样的,因为都是地址
2.但是指向不同类型的指针是不能直接相互赋值的
3.这是为了避免用错指针
int main()
{
int a[] = {1,2,3,4,5,6,7,8};
int *p = a;
char *q = a;
p = q
return 0;
}
//两种指针类型不一样,所以无法等于;
//如果用int*类型操作char*类型;比如在int*类型下等于0;则char*类型中为4个char(sizeof)变为0;
指针的类型转换
void* 表示不知道指向什么东西的指针,计算时与char* 相同(但不相通)
指针也可以转换类型:
int i=0;
int *p = &i;
void *q = (void*)p;
//这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量(我不再当你是int,我认为你就是个void)
动态内存分配
输入数据时,先告诉数据个数,然后再输入,要记录每个数据
如果时c99是可以用变量来定义数组的大小的;
int *a = (int *)malloc(n *sizeof(int));
#include<stdio.h>
#include<stdlib.h>
int main()
{
int number;
int *a;
int i;
printf("输入数量:");
scanf("%d",&number);
//int a[number]; c99可以这么写
a = (int*)malloc(number*sizeof(int)); //和系统申请了一块内存
for(i=0;i<number;i++)
{
scacnf("%d",&a[i]);
} //输入数组
for(i=number-1;i>=0;i--)
{
printf("%d",a[i]);
} //逆序输出
free(a); //返回内存
return 0;
}
malloc
#include<stdio.h> //头文件
void* malloc(size_t size);
向malloc申请的空间的大小是以字节为单位的
返回的结果是void*,需要类型转换为自己需要的类型
(int*)malloc(n*size(int))
如果申请失败则返回0,或者叫做NULL; 你的系统能够给你多大的空间
#include<stdio.h>
#include<stdlib.h>
int main()
{
void *p;
int cnt = 0;
while( ( p=malloc(1024*1024*100) ) ) //1024为1k;1024个k就是一兆mb;再乘一百;把申请到的空间给P,while的条件就是:如果p得到的结果不是0,就循环继续cnt++;如果是0就停止;
{
cnt++;
}
printf("分配%d00mb的空间\n",cnt);
return 0;
}
free()
//把申请来的空间还给“系统”
//申请过的空间,最终都应该要还 混出来的,迟早都要还;
//只能换申请来的空间的首地址;
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
void *p;
int cnt = 0;
p = malloc(100*1024*1024);
p++;
free(p); //不行,p的值不是首地址
return 0;
}
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
int i;
void *p;
int cnt = 0;
p = &i;
free(NULL); //如果定义了一个指针一般初始化为NULL;如果最后没有对指针操作或者出错;则释放NULL;这种不会出错
return 0;
}
常见问题
//申请了没free---->长时间运行内存逐渐下降;
// 新手:忘了; 老手:找不到合适的free的时机
//free过了再去free;
//地址变过了,直接去free;
逃逸字符
字符 | 意义 | 字符 | 意义 |
\b | 回退一格 | \ " | 中间应当无空格(下同);意义是双引号“ |
\t | 到下一个表格位 | \ ' | 意义单引号' |
\r | 回车 | \ \ | 反斜杠本身 |
\n为换行符
变量的存储方式和生存期
动态存储方式与静态存储方式
从变量的作用域的角度来观察,变量可以分为全局变量和局部变量,还可以从变量值存在的时间(即生存期)来观察。有的变量在程序运行的整个过程中都是存在的,而有的变量则是在调用其所在的函数时才临时分配储存单元,而在函数调用结束后该存储单元就马上释放了,变量也就不存在了。也就是说变量有两种存储方式:静态储存方式和动态存储方式。
静态存储方式是指在程序运行期间由系统分配固定的存储空间的方式。
动态存储方式则是在程序运行期间根据需要进行动态的分配存储空间的方式·
内存中可供用户使用的存储空间的情况,这个空间可分为3部分 :1.程序区,2.静态存储区,3.动态存储区
数据放在分别放在静态存储区和动态存储区中。全局变量全部放在静态存储区中,在程序开始执行时给全局变量分配存储区,程序执行完毕就释放。在程序执行过程中它们占据固定的存储单元,而不是动态的进行分配和释放
在动态存储区中存放以下数据:
1.函数形式参数。在调用函数时给形参分配存储空间。
2.函数中定义的没有用关键字static声明的变量,即自动变量;
3.函数调用时的现场保护和返回地址
对于以上这些数据在函数调用时分配动态存储空间,函数结束时释放这些空间 ; 如果:在程序中两次调用同一函数,而在此函数中定义了局部变量,在两次调用时分配给这些局部变量的存储空间的地址可能不相同。
如果一个程序中包含了若干个函数,每个函数中局部变量的生存期不等于整个程序的执行周期,它只是程序执行周期的一部分。
在C语言中每一个变量和函数都有两种属性;数据类型和数据的存储类别。对数据类型来说;基本的有:整形、浮点型等。储存类别指的是数据在内存中存储的方式(如静态存储和动态存储)。
在定义和声明变量和函数时,一般应同时指定其数据类型和存储类别;也可以采用默认方式指定(即:用户不指定,系统会隐含地指定为某一种存储类别)。
C语言的存储类别包括4种:自动的(auto),静态的(static),寄存器的(register),外部的(extern)。
局部变量的存储类别
自动变量(auto变量)
函数中的局部变量,如果不专门声明为static(静态储存类别,都是动态地分配存储空间,数据存储在动态存储区中。函数中的形参和在函数中定义的局部变量(包括在复合语句中定义的局部变量),都属于此类;在调用函数时,系统会给这些变量分配存储空间,在函数调用结束后就自动释放这些空间。因此这类局部变量称为自动变量。
//声明
int f(int a) //定义f函数,a为形参
{
auto int b,c = 3; //定义b,c为自动变量
.
.
.
}
该函数运行完后自动释放a b c所占据的存储单元;
实际上,关键字auto可以省略,不写auto则隐含指定为“自动存储类别”。
静态局部变量(static变量)
有时候,希望函数中的局部变量的值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放。在下一次再调用该函数时,该变量已有值(就是上一次函数调用结束时的值)。这时就应该指定该局部变量为"静态局部变量",用关键字static进行声明。
(其实是一种全局变量;但是有作用域,生存期是全局)
such as:
#include<stdio.h>
int main()
{
int f(int); //函数声明
int a = 2,i; //自动局部变量
for(i=0;i<3;i++)
{
printf("%d\n",f(a));
}
return 0;
}
int f(int a)
{
auto int b = 0; //自动局部变量
static int c = 3; //静态局部变量
b+=1;
c+=1;
return (a+b+c);
}
//c是静态变量函数调用结束后,它并不释放,保留其当前值
对静态变量是在编译时赋初值,即只赋一次,以后都是保留上次函数调用结束后的值
字符串
charword[]={'h','e','l','l','o','\0'};
字符;'';
字符串:" ";结尾默认\0
或者char*s="Dasdadasdasd";
%s输出字符串;
字符串操作
putchar函数
intoutchar(intc);
//向标准输出写一个字符(int类型,只能接受一个;)
//返回写了几个字符,EOF (-1)表示写失败;
getchar函数
intgetchar (void);
//从标准输入读入一个函数
//返回类型是int是为了返回EOT(-1)
结束函数时:
·windows-->Ctrl-Z
·Unix-->Ctrl-D
示例程序
#include<stdio.h>
intmain(intargc,charconst*argv[])
{
intch;
while((ch=getchar()) !=EOF )
{
putcahr(ch);
}
printf("EOF\n");
return0;
}
为什么要按会车之后才执行,在按下回车之前
在键盘上的字还要经过一个程序————shell;
字符串数组
chara[][10] = {"adada","asddas","qwertsasdfgc"};
//这个是有十个字符串数组a[0]~a[n];每个都有10个字符;实际在里面的可能没用完;但是如果超出10个就会产生错误;
char*a[] //他的每一个元素比较小;但是里面的都是一个指针;它指向外面的一个地方用来存放字符串;
程序参数
·intmain(intargc,charconst*argv[])
·argv[0]是命令本身
·当使用Unix的符号连接时,反映符号连接的名字
示例
将该文件命名为abc.c
#include<stdio.h>
intmain(intargc,charconst*argv[])
{
inti=0;
for(;i<argc;i++)
{
printf("argv[%d]=%s\n",argv[i]);
}
return0;
}
使用参数时;要用命令来使用:以ubuntu为例 such as: gcc -v -std=c99 abc.c -g -o abc 进行编译后运行时
./abc 123 123 123
结果是:
argv[0] = ./abc
argv[1] = 123
argv[2] = 123
argv[3] = 123
字符串函数
头文件#include <string.h>
strlen函数
// ·size_t strlen(const char* s)
// ·返回s字符串的长度(不包括结尾的0)
#include<stdio.h>
#include<string.h>
int mian (int argc,char const *argv[])
{
char line[] = "Hello";
printf("strlen=%lu\n",strlen(line)); //运行结果5
printf("sizeof=%lu\n",sizeof(line)); //运行结果6
return 0;
}
为了不让保证穿进去的字符串不被修改;可以用另一种写法;
#include<stdio.h>
#include<string.h>
int mylen(const char *s)
{
int idx = 0; //设置一个累加数;同时也是下标;
while(s[idx] != '\0') //遍历字符串直到 /0
{
idx++;
}
return idx;
}
int mian (int argc,char const *argv[])
{
char line[] = "Hello";
printf("strlen=%lu\n",mylen(line)); //运行结果5
printf("sizeof=%lu\n",sizeof(line)); //运行结果6
return 0;
}
strcmp函数
· int dtrcmp (const char *s1,const char *s2)
·比较两个字符串,返回:
·0 :s1==s2
·1 :s1>s2
·-1 : s1<s2
功能:判断两个字符串是否相当
用法: strcmp(数组名,数组名);
结果表示
若数组相同则为0,
若为负值,则前一个数组比后一个数组小 ;
若为正值,则前一个数组比后一个数组大;
返回的就是差值;
编码的大小;也可以理解为在字母表的位置;
char s1[] = "abc";
char s2[] = "abc";
char s3[] = "abb";
printf("%d\n",strcmp(s1,s3)); //结果为 —1
printf("%d\n",strcmp(s1,s2)); //结果为 0;
printf("%d\n",'a'-'A'); //结果为32;
if( strcmp(s1,s2)==0 )
{
printf("s1=s2");
}
else
{
printf("s1!=s2");
}
return 0;
自己写一个相同的功能的函数
#include<stdio.h>
#include<string.h>
int mycmp(const char *s1,const char *s2)
{
int idx = 0;
while(1)
{
if( s1[idx] != s2[idx])
{
break;
}
else if( s1[idx] == '\0')
{
break;
}
idx++;
}
return s1[idx] - s2[idx];
}
//第二种比较简洁的
int mycmp(const char *s1,const char *s2)
{
int idx = 0;
while( s1[idx] == s2[idx] && s1[idx] != '\0')
{
idx++;
}
return s1[idx] - s2[idx];
}
//第三次改进
int mycmp(const char *s1,const char *s2)
{
while(*s1 == *s2 && *s1 != '\0'){ //直接动指针;使指针++;
s1++;
s2++;
}
return *s1 - *s2;
}
strcpy函数
另一种写法strncpy(n代表数字代表拷贝几个)
功能:拷贝字符串
char *strcpy(char *restrict dst, const char *restrict src, int);
将src的字符串拷贝到dst中
restrict表明src和dst不重叠 c99
返回dst
·为了能链起代码来
char *dst = (char*)malloc(strlen(src)+1); //为des申请空间,又因为strlen是纯内容的长度不包括\0;所以要加一;
strcpy(dst,str);
示例:
char a1[]="abcdw";
char a2[]="a";
strcpy(a2,a1,int);//此处int为拷贝前几位字符
printf("%s\n",a2);
return 0;
运行结果:abcdw
自定义函数
//自己写一个功能相同的函数
数组版:
char* mycpy(char *restrict dst,char *restrict str)
{
int idx = 0;
while(str[idx] != '\0')
{
dst[idx] = str[idx];
idx++;
}
dst[idx] = '\0';
return dst;
}
//指针版:
char *mycpy(char *restrict dst,char *restrict str)
{
while( *str != '\0')
{
char *rets = dst;
*dst = *str;
*str++;
*dst++;
}
return rets;
}
strcat函数
另一种写法 strncat
功能将第二个字符串拷贝到第一个后面;第一个必须有足够的空间;返回第一个字符串;
格式:strcat (第一个字符串变量名,第二个字符串变量名)
char a1[]="abcdw";
char a2[]="12345";
strcat(a1,a2); //预期结果:abcdw12345
printf("a1=%s",a1);
结果正确;
strcat和strcpy可能有安全问题;原因是空间大小不确定;
strchr函数
strrchr :从右边找过来;
功能:寻找字符在字符串中的位置:
写法:char *strchr(const char *s,int c);
返回NULL代表没有找到;
不知道为什么我有点不一样;
示例
char a1[]="abcdw12345";
int c='c';
printf("%s",strchr(a1,c));
#include<stdio.h>
#include<string.h>
int main(int argc,char const *argv[])
{
char s[] = "hello";
char *p = strchr(s,'l');
printf("%s\n",p); //结果是llo;也就是它返回了指向第一个'l'的指针;
//如果我们想找第二个'l'的位置
p = strchr(p+1,'l');
printf("%s\n",p); //结果是lo;
//如果我们要将该字符后面的内容去复制到另外一个字符串里面去;
char *t = (char*)malloc(strlen(p)+1); //我们不知道大小所以用malloc来申请空间,加一是因为strlen不包括'\0'
strcpy(t,p); //将p里的内容复制到t中;
printf("%s\n",t);
free(t); //释放内存;
//如果我们要的是前面的那一段;
char *c = p; //用一个变量c来临时存一下*p;
*p = '\0';
char *t = (char*)malloc(strlen(s)+1);
strcpy(t,s); //*p变为\0剩余前面的内容,所以将s复制到t中即可;
printf("%s\n",t);
*p = *c; //恢复字符串s
free(t); //释放空间
return 0;
}
如果不存在则返回NULL;存在时他会中从该字符第一次出现时打印;
此次结果为 cdw12345;
若将%s换成%d则输出为0;
· char *strstr(const char *s1,const char *s2) //字符串中找字符串;
· char *strcasestr(const char *s1,const char *s2) //忽略大小写寻找字符串;
ACLLIB的基本图形函数
ACLLib介绍
· 是一个基于Win32API的函数库,提供了相对较为简单的方式来做一个Windows程序
· 实际提供了一个.c和两个.h ,可以在MSVC 和Dev C++(MinGW)中使用
· 一GPL方式开源放在github上
windos API
· 从第一个32位的Windows开始就出现了,就叫Win32API
· 它是一个纯C的函数库,就和C标准库一样,使你可以写Windows应用程序
· 过去很多Windows程序是用这个方式做出来的
main
· main()成为C语言的入口函数其实和C语言本身无关,你的代码是被一小段叫做启动代码的程序所调用的,它需要一个叫做main的地方
· 操作系统把你的可执行程序装载到内存例,启动运行,然后调用你的main函数
WinMain ( )
· As main() is the entry function of an ordinary C program, WinMain() is the one in Win32API program.
· Windows applications have a different "startup" code that needs a function "WinMain()"
#include<windows.h>
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTRlpCmdLine, int nCmdShow)
{
MessageBox(NULL,"GOODbye,cruel world","Note",MB_OK);
return 0;
}
· 如何产生一个窗口 ————————————————窗口结构
· 如何在窗口中画东西 —————————————— DC
· 如何获得用户的鼠标和键盘动作————消息循环和消息处理代码
·如何画出标准的界面:菜单、按钮、输入框
·acllib目前不能做
窗口结构
消息循环
消息处理
枚举值
枚举是一种用户定义的数据类型,它用关键字enum声明
也放在预处理阶段;
enum 枚举名 //枚举名是可以省略的;
{
枚举值, //其中变量都是int类型
枚举值2 //若无赋值,则默认从0开始
}; //最后要用分号结尾;
enum week
{
day1=1,
day2=2,
day3,
};
#include<stdio.h>
enum color { red, yellow, green};
void f(enum color c); //做形参时也要加上enum
int main(int argc,char const *argv[])
{
enum color t = red; //enum可以看作是int ;
scanf("%d",&t);
f(t);
return 0
}
void f(enum color c) //必须加上color;
{
printf("%d",c);
}
结构
声明结构类型
我们在定义一个新的结构类型;当我们声明出一个结构类型来,我没可以用它定义出很多结构变量;
用我自己的理解来说就是,我定义了一个结构类型,并命名为point;我们用struct+命名构成了一个新结构(里面的东西全由自己定义);然后struct + 结构命名 +变量名;
#inlcude<stdio.h>
struct date{
int month; //可以叫做结构成员;
int day;
int year;
}; //在外部声明;
int main(int argc,char const *argv[])
{
struct date{
int month;
int day;
int year;
}; //这是一条声明语句;所以需要一个分号
struct date today; //一个普通的结构体变量
today.month = 3;
today.day = 1;
today.year = 2022;
printf("日期:%i-%i-%i.\n",today.year,today.month,today.day);
return 0;
}
和本地变量一样,在函数内部声明的结构类型只能在函数内部使用, 所以通常在函数外部声明结构类型,这样就可以被多个函数使用了;
结构声明
struct point{
int x;
int y;
};
struct point P1,P2; //变量是p1和p2;p1和p2里面都有x和y的值;
//第二种声明方法
struct{
int x;
int y;
}p1,p2; //p1和p2都是一种无名结构,里面有x和y;
//第三仲:
struct point{
int x;
int y;
}p1,p2; //p1和p2都是point里面有x和y的值t;
结构的初始化
#include<stdio.h>
struct date{
int month;
int day;
int year;
};
int main(int argc,char const *argv[])
{
struct date todaty = {07,31,2014}; //对应位置赋值
struct date thismonth = {.month = 7;.year = 2014}; //按命名赋值。但是没有赋值的默认为0;
return 0;
}
结构成员
· 结构和数组有点像;
·但是数组的单元必须都是相同类型,而结构的成员可以是不同类型的;
·数组用[]运算符和下标访问其成员
·a[0] = 10;
· 结构用 . 运算符和名字访问其成员
·today.day
·student.firstName
·pl.x
·p1.y
结构运算
· 要访问整个结构,直接用结构变量的名字
· 对于整个结构,可以做赋值、取地址,也可以传递给函数参数
· p1 = (struct point){5,10}; //相当于p1.x = 5; p1.y = 10;
· p1 = p2; //相当于p1.x = p2.x; p1.y = p2.y;
#include<stdio.h>
structdate{
intmonth;
intday;
intyear;
};
intmain(intargc,charconst*argv[])
{
structdatetoday;
today= (structdate){07,3,2014};
structdateday;
day=today;
return0;
}
结构指针
·和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符;
·structdate*pDate=&today;
intmain()
{
structdatetoday;
structdate*pDate=&today
return0;
}
结构与函数
· 结构作为函数的参数
int unmberOfDays(struct date d) //整个结构可以作为参数的值传入函数
· 这时候是在函数内新建一个结构变量,并复制调用者的结构的值
· 也可以返回一个结构
· 这与数组完全不同
#include<stdio.h>
#include<stdbool.h>
structdate {
intmonth;
intday;
intyear;
};
boolisLeap(structdated);
intnumberOfDays(structdated);
intmain(intargc,charconst*argv[])
{
structdatetoday,tomorrow;
printf("Enter today's date (mm dd yyyy):");
scanf("%i %i %i",&today.month,&today.day,&today.year);
if( today.day!=numberOfDays(today) )
{
tomorrow.day=today.day+1;
tomorrow.month=today.month;
tomorrow.year=today.year+1;
}elseif( today.month==12 )
{
tomorrow.day=1;
tomorrow.day=1;
tomorrow.day=today.year+1;
}else{
tomorrow.day=1;
tomorrow.month=today.month+1;
tomorrow.year=today.year;
}
printf("Tomorrow's date is %i-%i-%i.\n",tomorrow.year,tomorrow.month,tomorrow.day);
return0;
}
intnumberOfDays(structdated)
{
intdays;
constintdaysPerMonth[12] = {31,28,31,30,31,30,31,31,30,31,30,31};
if( d.month==2&&isLeap(d))
{
days=29;
}else
{
days=daysPerMonth[d.month-1];
}
returndays;
}
boolisLeap(structdated)
{
boolleap=false;
if( (d.year%4==0&&d.year%100!=0) ||d.year%400==0)
{
leap=true;
}
returnleap;
}
· 对于结构来说没有直接的方式可以scanf和printf一个结构,如果我们打算写一个函数读入一个结构
#include<stdio.h>
struct point{
int x;
int y;
};
void getStruct(struct point); //该函数在里面有一个结构变量,然后进行传参;
void output(struct point); //打印;
int main(int argc,char const* argv[])
{
struct point y = {0,0};
getStruct(y); //把y的值传进去;并进行赋值;
output(y); //再次打印y的值,发现还是 0和0
return 0;
}
void getStruct(struct point p)
{
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d %d",p.x,p.y);
}
void output(struct point p)
{
printf("%d %d",p.x,p.y);
}
//我们读入的结构如何传回???
· c在函数调用时是传值的(所以在函数中的p和main中的y是不同的, 在函数读入了p的值之后,没有任何东西回到main所以y还是0,0)
解决方案
· 之前的方案,把一个结构传入了函数,然后在函数中操作,但是没有返回回去
· 问题在于传入函数的是外面那个结构体的克隆体,而不是指针
· 传入结构和传入数组是不同的
· 在这个输入函数中,完全可以创建一个临时的结构变量,然后把这个结构返回给调用者;
void main()
{
struct point y = {0,0};
y = inputPoint();
output(y);
}
struct point int putPoint()
{
struct point temp;
scanf("%d",&temp.x);
scanf("%d",&temp.y);
return temp;
}
代码如下:
#include<stdio.h>
struct point{
int x;
int y;
};
struct point getStruct(void);
void output(struct point);
int main(int argc,char const* argv[])
{
struct point y = {0,0};
y = getStruct();
output(y);
}
struct point getStruct(void)
{
struct point p;
scanf("%d",&p.x);
scanf("%d",&p.y);
printf("%d,%d",p.x,p.y);
return p;
}
void output(struct point p)
{
printf("&d,%d",p.x,p.y);
}
指向结构的指针(更好的方案)
struct date {
int month;
int day;
int year;
}myday;
struct date *p = &myday;
(*p).month = 12;
p->month = 12;
// p ---> month = 12;用->表示指针所指的结构变量中的成员;
结构指针参数
void main ()
{
struct point y = {0,0};
intputPoint(&y);
output(y);
}
struct point *inputPoint(struct point *p)
{
scanf("%d",&(p->x));
scanf("%d",&(p->y));
return p;
}
代码如下:
#include<stdio.h>
struct point{
int x;
int y;
};
struct point *getStruct(struct point *);
void output(struct point);
void print(const struct point *p);
int main(int argc,char const *argv[])
{
struct point y = {0,0};
getStruct(&y);
output(y);
output(*getStruct(&y) );
print(getStruct(&y) );
}
struct point* getStruct(struct point *p)
{
scanf("%d",&(p->x));
scanf("%d",&(p->y));
printf("%d,%d",p->x,p->y);
return p;
}
void output(struct point p)
{
printf("%d,%d",p->x,p->y);
}
//* getSruct(&y) = (struct point ){1,2}; 甚至可以赋值;
结构中的结构
结构数组
struct date dates[100];
struct date dates[] = {
{4,5,2005},{2,4,2005}
};
示例程序
#include<stdio.h>
struct time{
int hour;
int minutes;
int seconds;
};
struct time timeUPdate(struct time now);
int main(void)
{
struct time timeTimes[5] = {
{11,59,59},{12,0,0},{1,29,59},{23,59,59},{19,12,27}
};
int i;
for(i = 0;i<5;++i){
printf("Time is %.2i:%.2i:%.2i",timeTimes[i].hour,timeTimes[i].minutes,timeTimes[i].seconds);
testTimes [i] = timeUPdate(timeTimes[i]);
printf("... one second later it%.2i:%.2i:%.2i",
timeTimes[i].hour,timeTimes[i].minutes,timeTimes[i].seconds);
}
return 0;
}
struct time timeUPdate(struct time now)
{
++now.seconds;
if( now.seconds == 60 ){
now.seconds = 0;
++now.mihutes;
if( now.minutes == 60 ){
now.minutes = 0;
++now.hour;
if( now.hour == 24 ){
now.hour = 0;
}
}
}
return now;
}
结构中的结构
结构中的变量也可以是结构
struct dateAndTime{
struct date sdate;
struct time stime;
}
示例:
struct point{
int x;
int y;
}
struct rectangle{
struct point pt1;
struct point pt2;
};
如果有变量 struct rectangle r; 就可以有r.pt1.x r.pt1.y r.pt2.x r.pt2.y
如果变量有定义:
struct rectangle r,*rp;
rp = &r;
那么下面的四种形式是等价的
r.pt1.x
rp->pt1.x
(r.pt1).x
(rp->pt1).x
但是没有rp->pt1->x (因为pt1不是指针)
结构中的结构中的数组
#include<stdio.h>
struct point
{
int x;
int y;
};
struct rectangle{
struct point p1;
struct point p2;
};
void printRect(struct rectangle r)
{
printf("<%d,%d> to <%d,%d>\n ",r.p1.x,r.p1.y,r.p2.x,r.p2.y);
}
int main(int argc,char const *argv[])
{
int i;
struct rectangle rects[] = {{{1,2},{3,4}},{{5,6},{7,8}}}; //2 rectangle
for(i = 0;i<2;i++){
printRect(rects[i]);
}
}
自定义数据类型(typedef)
· C语言提供了一个叫做typedef的功能来声明一个已有的数据类型的新名字。比如:
typedef int Length
使得Length成为int类型的别名。
· 这样,Length这个名字就可以代替int出现在变量定义和参数声明的地方了
Length a,b,len;
Length numbers[10];
声明新的类型的名字
· 新的名字是某种类型的别名
· 改善了程序的可读性
typedef long int64_t;
typedef struct ADate{
int month;
int day;
int year;
} Date;
int64_t i = 100000000000;
Date d = {9,1,2005};
typedef int Length; //Length就等价于int类型
typedef char *Strings[10]; // Strings是10个字符串的数组的类型
typedef struct node {
int date;
struct node *next;
}aNode; //aNode就变成了node的新名字
或者:
typedef struct node aNode; //用aNode代替dtruct node
联合
· 存储
· 所有的成员共享一个空间
· 同一时间只有一个成员是有效的
· union的大小是其最大的成员
· 初始化
·对第一个成员初始化
#include<stdio.h>
typedefunion{
inti;
charch[sizeof(int)];
} CHI;
intmain(intargc,charconst*argv[])
{
CHIchi;
inti;
chi.i=1234;
for( i=0; i<sizeof(int); i++){
printf("%2hhX",chi.ch[i]);
}
printf("\n");
return0;
}
计算结果是000004D2
但是运行结果是D2040000;小端法存放;低地址的在前;
链表
可变数组
·特点1.数组可以变大
·二。我可以看到它目前多大
·三。我们要能访问它其中的单元
#ifndef _ARRAY_H_
#define _ARRAY_H_
typedefstruct{
int*array;
intsize;
} Array;
Arraya;
Arrayarray_create(intinit_size); //用来创建一个数组
voidarray_free(Array*a); //用来回收数组空间
intarray_size(constArray*a); //看数组里有多少个单元可以用
int*array_at(Array*a,intindex); //用来访问数组中的某个单元
voidarray_inflate(Array*a,intmore_size); //让数组长大
#endif
#include "array.h"
#include "stdio.h"
#include "stdlib.h"
//typedef struct{
// int *array;
// int size;
//} *Array;
const BLOCK_SIZE = 20;
Array array_create(int init_size) //用来创建一个数组
{
Array a;
a.array = (int*)malloc( sizeof(int) * init_size);
a.size = init_size; //a ->size是数组的单元数量
return a;
}
void array_free(Array *a) //回收数组空间
{
free(a->array);
a->array = NULL;
a->size = 0;
}
int array_size(const Array *a) //只有一条指令,(封装);函数作用输出数组大小
{
return a->size;
}
int *array_at(Array *a, int index) //访问数组中的某个单元
{
if(index >= a->size){ // 用来防止访问数组时越上界
array_inflate(a, (index/BLOCK_SIZE+1)*BLOCK_SIZE - a->size); //BLOCK_SIZE是20;index除以BLOCK_SIZE用来算出位于那个BLOCK_SIZE中,然后加一去乘BLOCK_SIZE用来去到下一个BLOCK_SIZE,最后减去a->size;
}
return &(a->array[index]);
}
void array_inflate(Array *a, int more_size) //数组空间增长
{
int *p = (int*)malloc(sizeof(int)(a->size + more_size)); //申请一个新的空间
int i;
for ( i = 0;i<a->size; i++){ //将之前的内容复制到新空间中
p[i] = a->array[i];
}
free(a->array); //释放之前的空间
a->array = p; //
a->size += more_size; // a->size = a->size + more_size;
}
int main(int argc, char const* argv[])
{
Array a = array_create(100); //创建一个大小为100个int的空间;
printf("%d\n",array_size(&a)); //输出数组大小;
printf("%d\n",*array_at(&a,0)); //访问数组中的第一个单元;即a[0];
int number; //number是判断条件
int cnt = 0; //计数作用
while(1){ //可以自动增长;
scanf("%d",&number);
if( number != -1){
*array_at(&a,cnt++) = number;
}
//scanf("%d",array_at(&a,cnt++)); //一直访问数组单元的死循环;
}
array_free(&a); //释放空间
return 0;
}
可变数组的缺陷
越往后拷贝越花时间,而且可能你的内存够用但是缺无法申请;原因是原有的内容占据的空间无法提前使用,那么我们是否能在原有的地方往后链上去(地址不一定连续);
链表
//有一种东西;分为两个部分一部分是内容一部分是一个指针,这个指针它指向下一个同样的东西;直到最后一个(他的结尾是一个符号标志着结尾),当然还有一个指针指向第一个东西用来表明这是头;这种就叫做链表,
//每一个东西我们叫做结点,每一个结点都是两块东西,一块是数据(数据域)一块是指向下一个的指针(指针域);
//每个结点中只包含一个指针域的链表又被称为 线性链表 或 单链表。
又一个.h文件
/*node.h*/
#ifndef _NODE_H_
#define _NOOE_H_
typedef struct _node{
int value; //数据域
struct _node *next; //指针域
}Node;
#endif
示例程序
#include "node.h"
#include "stdio.h"
#include "stdlib.h"
//typedef struct _node{
// int value;
// struct _node *next;
//}Node;
intmain(intargc, charconst*argv[])
{
Node*head=NULL; //指向开头的头结点
intnumber; //数据域
do{
scanf("%d",&number); //输入要存储的数据
if( number!=-1){
//加到链表
Node*p= (Node*)malloc(sizeof(Node)); //创建一个结点,每次都要申请一段空间大小为sizeof(Node),用来创建新结点,注意循环在中;
p->value=number; //将number赋值给p中的value;也就是填充数据域;
p->next=NULL; //将现在的最后一个结点的指针指向下一个新的结点,并且下一个结点的指针指向NULL
Node*last=head; //定义了一个*last用来等于head
if ( last ){
while( last->next ){ //如果last 也就是head不是NULL就进入循环
last=last->next;}
last->next=p; //最后一个应该是NULL;
}else
{
head=p;
}
}
}while ( number!=-1 );
return0;
}
链表的函数
#include "node.h"
#include "stdio.h"
#include "stdlib.h"
//typedef struct _node{
// int value;
// struct _node *next;
//}Node;
voidadd(Node*head, intnumber); //定义的函数作用是:链表的创建和使用
intmain(intargc, charconst*argv[])
{
Node*head=NULL; //指向开头的
intnumber;
do{
scanf("%d",&number);
if( number!=-1){
add(head,number); //自定义的函数add
}
}while ( number!=-1 );
return0;
}
voidadd(Node*haed,intnumber )
{
//加到链表
Node*p= (Node*)malloc(sizeof(Node)); //对p进行初始化,p是一个Node结构体指针;里面有一个value和一个*next;
p->value=number; //将number赋值给p中的value;
p->next=NULL; //将现在的最后一个结点的指针指向下一个新的结点,并且下一个结点的指针指向NULL
Node*last=head;
if ( last ){
while( last->next ){
last=last->next;
}
last->next=p; //最后一个应该是NULL;
}else
{
head=p;
}
}
升级版
#include "node.h"
#include "stdio.h"
#include "stdlib.h"
typedef struct _list{ //定义新结构。里面只有一个Node head
Node *head;
}List;
void add(Node *head, int number);
int main(int argc,cahr const *argv[])
{
List list;
int number;
list.head = NULL;
do{
scanf("%d",&number);
if(number != -1){
head = add(&list,number);
}
}while(number != -1);
return 0;
}
void add(List pList,int number )
{
//加到链表
Node *p = (Node*)malloc(sizeof(Node)); //对p进行初始化,p是一个Node结构体指针;里面有一个value和一个*next;
p->value = number; //将number赋值给p中的value;
p->next = NULL; //将现在的最后一个结点的指针指向下一个新的结点,并且下一个结点的指针指向NULL
Node *last = head;
if ( last ){
while( last->next ){
last = last->next;
}
last->next = p; //最后一个应该是NULL;
}else
{
Plist->head = p;
}
}
遍历方法
void 函数名(List *list){
Node *p;
for(p = list.head;p;p = p->next){ //将p变为下一个结点的(指针域),如果p(指针域)变成了NULL就停止循环;
printf("%d\t",p->value); //打印数据域中的内容;
}
printf("\n")
}
搜索
for(p=list.head; p; p->next){
if( p->value==number){
printf("找到了");
}
else{
printf("没找到");
}
break;
}
删除
Node *q;
for( q = NULL,p = list.head; p ;q = p,p = p->next ){
if(p->value == number ){
q->next = p->next;
free(p);
break;
}
}
插入:
void add(List pList,int number )
{
//加到链表
Node *p = (Node*)malloc(sizeof(Node)); //对p进行初始化,p是一个Node结构体指针;里面有一个value和一个*next;
p->value = number; //将number赋值给p中的value;
p->next = NULL; //将现在的最后一个结点的指针指向下一个新的结点,并且下一个结点的指针指向NULL
Node *last = head;
if ( last ){
while( last->next ){
last = last->next;
}
last->next = p; //最后一个应该是NULL;
}else
{
Plist->head = p;
}
}
判断溢出
int sum(unsigned x,unsigned y)
{
int ret=0;
unsigned sum = x+y;
if(sum>x){
if(sum>y){
ret=1;
}
else{
ret=0;
}
}
else{
ret=0;
}
return ret;
}
ctype.h
isspace(被检查的参数c) //函数用来判断是否为空白字符
检查参数c是否为空白字符,也就是判断是否为空格(' ')、水平定位字符
('\t')、归位键('\r')、换行('\n')、垂直定位字符('\v')或翻页('\f')的情况。
返回值
若参数c为空格字符,则返回非0,否则返回0。
编译预处理指令
#开头的是编译预处理指令
它们不是c语言成分,但是c离不开它们;
#define来定义一个宏
· #define <名字> <值>
· 注意没有结尾的分号,因为不是c的语句
· 名字必须是一个单词,值可以是各种东西
· 在C语言的编译器开始编译之前,编译预处理程序(cpp)会把程序中的名字换成值
· 完全的文本替换;
.c -> .i -> .s -> .o ->a.out
.i是将预编译处理完的文件
.s 是汇编代码文件
.o是目标代码文件
.o经过链接等变成了可执行文件
像函数的宏
· define cude(x) ((x)*(x)*(x))
· 宏可以带参数
· 一切都要括号
· 整个值要括号
·参数出现的每个地方都要括号
·#define RADT(x) ((x)*5729578)
· 可以带多个参数
·#define MIN(a,b) ((a)>(b)?(b):(a))
·也可以组合(嵌套)使用其他宏
·在大型程序的代码中使用非常普遍,
·可以非常复杂,如“产生”函数
·在#和##两个运算符的帮助下
·存在中西方文化差异
· 部分宏会被inline函数代替
多个.c文件
· main()里的代码太长了。适合分成几个函数
· 一个源代码文件太长了适合分成几个文件
· 两个独立的源代码不能编译形成可执行的程序
项目
· 在dev c++中新建一个项目,然后把几个源代码文件加入进去
· 对于项目,DEV C++的编译会把一个项目中所有的源代码文件都编译后,链接起来
· 有的IDE有分开的编译和建构两个按钮,前者是对单个源代码文件编译,后者是对整个项目做链接;
编译单元
·一个.c文件是一个编译单元
·编译器每次处理只能处理一个编译单元
#include的
" "还是<>
·#include有两种形式来指出要插入的文件
·""要求编译器首先在当前目录(.c文件所在的目录)寻找这个文件,如果没有,到到编译器指定的目录去找
·<>让编译器只在指定的目录去找
·编译器自己知道自己的标准库的头文件在哪里
·环境变量和编译器命令行参数也可以指定寻找头文件的目录
#include的误区
·#include不是用来引用库的
·stdio.h里只是有printf的原型,printf的代码在另外的地方,某个.lib(WIN)或.a(UNIX)中
·现在的C语言编译器默认会引入所用的标准库
·#include<stdio.h>只是为了让编译器知道printf函数的原型,保证你调用时给出的参数值时正确的类型
头文件
·在使用和定义这个函数的地方都应该#include这个头文件
·一般的做法就是任何.c都有对应的同名的.h,把所有对外公开的函数原型和全局变量的声明都放进去
不对外公开的函数
· 在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
· 在全局变量前面加上static就使得它成为只能在所在的编译单元中被使用的全局变量
externintgall; //放在.h文件中用来使得任意.c文件可以使用定义的该变量
变量的声明
·inti; 是变量的定义;
·externinti;是变量的声明;
声明是不产生代码的东西
·函数原型
·变量声明
·结构声明
·宏声明
·枚举声明
·类型声明
·inline声明
·定义产生代码的东西
重复声明
·同一个编译单元里,同名的结构不能被重复声明
·如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次
·所以需要“标准头文件结构”
头文件
· 只有声明可以被放在头文件中
·是规则不是法律
·否则会造成一个项目中多个编译单元里有重名的实体
·* 某些编译器允许多个编译单元中存在同名的函数,或者用weak修饰符来强调这种存在;
标准头文件结构
#ifndef _MAX_H_ //判断之前是否有 _MAX_H_H
#define _MAX_H_ //如果没有则定义一个,如果有则从此处到#endif都无效
int max(int a,int b);
extern int gall;
struct NODE{
int date;
char *name;
struct NODE* next;
};
#endif
· 运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次
· #pragma once 也能起到相同的作用,但不是所有编译器都支持
调用其他.c文件的变量
1.在一个.c文件中定义声明;
2.然后再.h文件中利用 extern 声明
3.引用.h文件
函数指针及其应用
#include <stdio.h>
voidf(){
}
intmain()
{
inti=0;
int*p=&i; //正常指针
void (*pf) (void) =f; //函数指针: 函数类型 (指针名称) (参数类型) = 函数符号;
f(); //正常调用函数
(*pf)(); //利用指针调用函数
printf("&i = %p",i);
printf("main=%p",main);
printf("f = %p",f);
return0;
}
//函数也是有地址的
函数指针的妙用;
#include<stdio.h>
voidf(inti){
printf("f() = %d",i);
}
intg(inti){
printf("f() = %d",i);
}
inth(inti){
printf("f() = %d",i);
}
intmain()
{
inti;
scanf("%d",&i);
------------------------
if(i==0){
f(i);
}elseif(i==1){
g(i);
}elseif(i==2){
h(i);
}
-----------------------
switch (i){
case0: f(i);break;
case1: g(i);break;
case2: h(i);break;
}
-----------------------
void (*pf[]) (int) = {f,g,h};
if( i>=0&&i<sizeof(pf)/sizeof(pf[0]) )
{
(*pf[i])(0);
}
retrun0;
}
程序二:
#incldue<stdio>
intplus(inta,intb){
returna+b;
}
intminus(inta,intb){
returna-b;
}
voidcal(int (*f)(int,int))
{
printf("%d\n",(*f)(2,3));
}
intmain(){
cal(plus);
cal(minus);
return0;
}
回调函数
#include <stdio.h>
#include "acllib.h"
voidmouseListener(intx,inty,intbutton,intevent)
{
printf("x = %d,y = %d,button = %d,event = %d\n",x,y,button,event);
}
intmain()
{
registerMouseEvent(mouseListener); //回调函数
return0;
}
⑴定义一个回调函数;
⑵提供函数实现的一方在初始化的时候,将回调函数的函数指针注册给调用者;
⑶当特定的事件或条件发生的时候,调用者使用函数指针调用回调函数对事件进行处理。
·typedefvoid(*KeyboardEventCallback)(constcharkey); //键盘可以读取功能键;有按下和抬起两种状态
·typedefvoid(*CharEventCallback)(intkey); //键盘上可读可写的字符收到一个就是一个;
·typedefvoid(*MouseEventCallback)(intx,inty,intbutton,intstatus);//鼠标的移动和点击
·typedefvoid(*TimerEventCallback)(inttimerID); //定时器
格式化输入输出
%[flags] [width] [.prec] [hlL] type | ||
flag | 含义 | |
- | 左对齐 | |
+ | 在前面放+或者- | |
(space) | 正数留空 | |
0 | 0填充 |
such as:
#include<stdio.h>
int main(int argc,char const *argv[])
{
printf("%-d\n",123); //123
printf("%+d\n",123); //+123
printf("%09d",123); //结果是000000123;九个空间,且用0填充
printf("%-9d",123); //左对齐且有九个空间
return 0;
}
width 或 prec | 含义 |
number | 最小字符数 |
* | 下一个参数是字符数 |
.number | 小数点后的位数 |
.* | 下一个参数是小数点后的位数 |
#include<stdio.h>
intmain()
{
printf("%9.2f\n",123.0); // 123.00 //.2代表小数点后两位,9代表输出9个空间位
printf("%*d\n",6,123); //将*换成6,空间6位;123不变对应%d 1
return0;
}
类型修饰 | 含义 |
hh | 单个字节 |
h | short |
l | long |
ll | long long |
L | long double |
intmain()
{
printf("%hhd\n",12345); //57; 12345的十六进制为0x3039;只取后两位(一个字节8位)为0x39 = 57;
}
%i也可以输出整数
%n的用法
intmain()
{
intnum;
printf("%d%n\n",12345,&num);
printf("%d\n",num); //结果是5;因为12345
intber;
printf("%dty%n",12345,&ber);
printf("%d\n",ber); //结果是7因为12345还有ty一个7个个数;
return0;
}
scanf的标准
flag | 含义 |
* | 跳过 |
数字 | 最大字符数 |
hh | char |
h | short |
l | long,double |
ll | long long |
L | long double |
#include<stdio.h>
intmain()
{
intnum;
scanf("%*d%d",&num); //输入123 456
printf("%d",num); //输出456;
return0;
}
%i的使用
scanf("%i",num); //可以输入任何进制的数:0x12;012;等等
printf("%d",num); //
scanf 和 printf存在返回值
intnum;
inti1=scanf("%d",&num);
inti2=printf("%d",num);
printf("%d,%d",i1,i2);
输入1234
i1=1;
i2=5;因为有一个回车;所以为5;
文件的输入与输出
文件的重定向:<和>
假设上一个c程序叫test
输入命令:./test>1.out;
将运行结果保存到1.out中;
假设有一个in.out文件里有输入的内容;
./test<in.out
将in.out中的内容作为输入
./test<in.out>1.out
将in.out作为输入的执行结果保存到1.out中
#include<stdio.h>
intmain(intargc,charconst*argv[])
{
FILE*fp=fopen("in.txt","r");
if(fp){
intnum;
fscanf(fp,"%d",&num);
printf("%d",num);
fclose(fp);
}else{
printf("无法打开文件");
}
return0;
}
//需要在同一个目录下的文件
fopen的参数
r | 打开只读 |
r+ | 打开读写从文件头开始 |
w | 打开只写,如果不存在则新建,如果存在则清空 |
w+ | 打开读写,如果不存在则新建,如果存在则清空 |
a | 打开追加,如果不存在则新建,如果存在则从文件尾开始 |
..x | 只新建,如果文件已存在则不能打开 |
二进制文件
· 其实所有的文件最终都是二进制的
· 文本文件无非是用最简单的方式可以读写的文件
·more tail
·cat
·vi
·而二进制文件是需要专门的程序来读写的文件
·文本文件的输入输出是格式化,可能经过转码;
文本vs二进制
· Unix喜欢用文本文件来做数据存储和程序配置
·交互式终端的出现使得人们喜欢用文本和计算机“talk”
·Unix的shell提供了一些读写文本的小程序
·Windows喜欢用二进制文件
·DOS式草根文化,并不继承和熟悉Unix文化
·PC刚开始的时候能力有限,DOS的能力更有限,二进制更接近底层
·文本的优势是方便人类读写,而且快平台
·文本的缺点是程序输入输出要经过格式化,开销大
·二进制的缺点是人类读写困难,而且不快平台
·int的大小不一致,大小端的问题
·二进制的优点是程序读写快
程序为什么要文件
·配置
·Unix用文本,Windows用注册表
·数据
·稍微有点量的数据都放数据库了
·媒体
·这个只能是二进制的
·现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了;
二进制读写
·size_t fread(void *restrict ptr,size_t size,size_t nitems,FILE *restrict stream);(指针也就是我们要读或者写的那块内存;这块内存有多大;有几个这样的内存;文件指针)
·size_t fwrite(const void *restrict ptr,size_t size,size_t nitems,FILE *restrict stream);
·注意FILE指针是最后一个参数
·返回的是成功读写的字节数
nitem
·因为二进制文件的读写一般都是通过对一个结构变量的操作来进行的
·于是nitem就是用来说明这次读写几个结构变量
补:一些概念:
1.形参是局部变量;
2.c语言中函数是程序的基本组成单位
3.c语言提供了6种位操作(<< ; >> ; ~ ; | ; &; ^ ;)
例题:
union hh{
char ch[6];
float a;
float b;
};
struct xx{
double w;
union hh hv;
float v[5];
}vh;
问 变量vh在内存中所占的字节数是(34)
联合体;见上文;
这篇关于C语言笔记,发上来保存一下的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!