C语言基础
慕课网+《C语言程序设计》+网络资料+自己的理解=请大家仅供参考
- c语言
- C程序运行所需要的过程
- 基本语法
- c语言指针
C语言基础
c语言
C程序运行所需要的过程
文本编写代码
编写文本代码,生成c或cpp文件,这时候它还是文本的;
预处理(这个可以没有,这是跨平台程序所需要的)
有时候,我们需要将程序在windows和Linux下都能够运行,只要用VS编译工具下编译一遍,在GCC编译工具下编译一遍。但是如果,有一种情况就一个程序有两个函数a()和b(),a()在Window下可以编译运行,但是b()不能运行的,b()在Linux下可以编译运行,a()不能运行,这就需要在编译之前对代码进行预处理。
预处理的形式如下:
#include <stdio.h>
#include “myHeader.h”
系统将自动调用预处理程序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译。
宏定义
文件包含
条件编译
complie
编译,就是compile,由c编译程序对你写的代码进行词法和句法分析,发现并报告错误,有错时编译不能通过。如若无错,则生成中间代码,扩展名为obj,此时它便是二进制的了;
link(build)
连接,在汇编里称link,在c里叫生成,即build,它的作用是生成可执行的exe文件。由于一个程序的源码可由多个文件组成。这些文件在第二步 中分别编译,生成各自的目标文件,这一步的作用便是将这些obj文件,以及程序中需要的其它库文件(dll除开),统一到一个文件中来,形成单个的exe 文件。此exe文件便可以在操作系统下直接运行了。
link将会把编译好的多个目标文件以及系统中的库和组件等,组合成一个可执行的程序
上述第二步做完中间代码生成的工作后还有个代码优化,这里就不详讲了。
execute
compile、build分别对应编译和连接这两个过程。execute的意思是执行,即生成了exe文件后运行它。如果你的程序有修改,点这一项它会 弹出窗口问你是否重新编译和连接,也就是它可以自动把前面的工作都做了。也就是说,vc菜单里的execute功能如下:
1.程序有修改时,或者没有做前面的编译连接工作时
execute=compile+build+执行;
2.程序编译连接好后
execute仅仅是执行,不再做前面两项工作。
当程序有修改时,选择execute只重编译和连接有改动的内容,但这样做有时候会出错,这时可选择build菜单下的rebuild all,重新编译、连接所有文件。
总之,c程序的生成可图示如下:
编写源代码->编译->连接,也叫生成->运行
对应的各个阶段处理的文件的扩展名为:
c或cpp->obj->exe。
c程序是编译执行的,还有一种程序是解释执行的,如java,它的源文件经过编译后形成扩展名为class的中间代码,之后不做连接生成exe文件,而是直接将这种.class的中间代码拿到java虚拟机平台上,由解释器解释执行。这种方式效率较低,但较灵活。
基本语法
#include
#include是文件包含命令,主要用来引入对应的头文件。#include的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成一个源文件,这与复制粘贴的效果相同。
例:
#include <stdio.h>
#include “myHeader.h”
“”和<>的区别,这只是搜索路径不一样,包含标准库的头文件建议采用<>,自定义头文件的建议采用””。
说明:
一个#include命令只能包含一个头文件,多个头文件需要多个#include命令。
文件包含允许嵌套,也就是说在一个被包含的文件中又可以包含另一个文件。
#include<stdio.h>头文件
#include <stdio.h>就是一条预处理命令,它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作
int main()主函数
是主运行函数,注:在最新的C标准中,main函数返回类型为int而不是void
个人解析
按照JAVA的思想解析的话,就是stdio.h这个头文件中封装int main()函数,其他想要调用的必须#include<stdio.h>
printf()打印输出语句
注释
多行注释: /* 注释内容 */
单行注释: //注释一行
注意:C程序一定是从主函数开始执行的。
变量
标识符(即为变量名)
(1)标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效,当两个标识符前8位相同时,则被认为是同一个标识符。
(2)标识符是严格区分大小写的。例如Imooc和imooc 是两个不同的标识符。
(3)标识符最好选择有意义的英文单词组成做到”见名知意”,不要使用中文。
(4)标识符不能是C语言的关键字。想了解更多C语言关键字的知识,请查阅WIKI。
变量名、变量值、存储单元
#define宏定义
宏定义的形式
#define 宏名 字符串
#表示这是一条预处理命令,所有的预处理命令都以#开头。define是预处理命令。宏名是标识符的一种,命名规则和标识符相同。字符串可以是常数、表达式等。
例:
#include <stdio.h>
- #define N 100
- int main(){
int sum = 20 + N;
printf(“%d\n”, sum);
return 0;
}
注意:这里的字符串只是一段序列,与C或者JAVA语言的中字符串不一样;
程序中可反复使用的表达式
宏定义 ,可以定义一个复杂的表达式的,调用时使用标识符(如:M),程序真正运行的时候,会用表达式代替标识符运行。
例:
#include <stdio.h>
- #define M (n*n+3*n)
- int main(){
int sum, n;
printf(“Input a number: “);
scanf(“%d”, &n);
sum = 3*M+4*M+5*M;
printf(“sum=%d\n”, n);
return 0;
}
C中的数据类型
基本数据类型
基本数据类型(char,int,float,double)
整型(int,short int,long int,unsigned int, unsigned short int,unsigned long int)
注:int、short int、long int是根据编译环境的不同,所取范围不同。而其中short int和long int至少是表中所写范围,但是int在表中是以16位编译环境写的取值范围。另外 c语言int的取值范围在于他占用的字节数 ,不同的编译器,规定是不一样。ANSI标准定义int是占2个字节,TC是按ANSI标准的,它的int是占2个字节的。但是在VC里,一个int是占4个字节的。
TC、VC是两种编译器
浮点型
浮点数据是指带小数的数字。
例
格式化输出语句
格式化输出语句,也可以说是占位输出,是将各种类型的数据按照格式化后的类型及指定的位置从计算机上显示。这样做的好处,是有利于计算机能够准确的将我们所要类型数据给我们。
格式
其格式为:printf(“输出格式符”,输出项);
常用格式化符
例
输出结果为: a=10
如果要输出多个变量的并指定变量的位置时候,格式符还可以连用,变量之间需要用逗号隔开,如:
输出结果为: 整数:10,小数:7.560000,字符:c
注意:格式符的个数要与变量、常量或者表达式的个数一一对应
常量
直接常量(字面量,是可以直接拿来使用,无需说明的量)
例
整型常量:13、0、-13;
实型常量:13.33、-24.4;
字符常量:‘a’、‘M’
字符串常量:” love you”
打开几种直接常量:
符号常量
在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。符号常量在使用之前必须先定义,其一般形式为:#define 标识符 常量值
* #define 标识符 常量值*
常量的标识符一般采用大写(用来区别常量与变量)
例
#include <stdio.h>
#define POCKETMONEY 10 //定义常量及常量值
int main()
{
//POCKETMONEY = 12; //小明私自增加零花钱对吗?
printf(“小明今天又得到%d元零花钱\n”, POCKETMONEY);
return 0;
}
类型转换
自动类型转换
自动转换规则
自动转换发生在不同数据类型运算时,在编译的时候自动完成。自动转换遵循的规则就好比小盒子可以放进大盒子里面一样,下图表示了类型自动转换的规则。
强制类型转换
强制类型转换是通过定义类型转换运算来实现的。其一般形式为:
* (数据类型) (表达式)*
其作用是把表达式的运算结果强制转换成类型说明符所表示的类型,例如:
输入结果:
注意:
1、数据类型和表达式都必须加括号,如把(int)(x/2+y)写成(int)x/2+y则成了把x转换成int型之后再除2再与y相加了。
2、转换后不会改变原数据的类型及变量值,只在本次运算中临时性转换。
3、强制转换后的运算结果不遵循四舍五入原则。
例
/*强制类型的转换:大类型转换小类型*/
double num=2.5;
printf(“num的整型部分是%d\n”,(int)num);//输出2
return 0;
运算符
※ 算术运算符
/*算术运算符*/
int x,y;
x=-10;
y=3;
printf(“x+y=%d\n”,x+y);//输出7
printf(“x-y=%d\n”,x-y);//输出13
printf(“x*y=%d\n”,x*y);//输出-30
printf(“x/y=%d\n”,x/y);//输出-3,int类型取整,不取余
printf(“x%y=%d\n”,x%y);//输出1,正负,要看被取余数是正是负
/*自增自减运算符*/
int x = 0;
printf(“x=%d\n”,x++);//输出0,因为先运算后自增
printf(“注意这里并不改变x的值%d\n”,x+1);//输出2
printf(“x=%d\n”,++x);//输出2,先自增后运算
※ 赋值运算符
简单赋值运算符(=)
复合赋值运算符(+=、-=、*=、/=、%=等)
复合赋值运算符就是在简单赋值符“=”之前加上其它运算符构成,例如+=、-=、*=、/=、%=
例
/*赋值运算符*/
int x = 10;
int y = 10;
//使用简单赋值语句实现x乘以2。
x=x*2;
//使用复合赋值语句实现y乘以2。
y*=2;
printf(“x=%d\n”, x);//输出20
printf(“y=%d\n”, y);//输出20
return 0;
※ 关系运算符
/*关系运算符*/
int x=5;
int y=5;
int z=10;
printf(“x是否大于y:%d\n”, x>y ); //x是否大于y, 输出0
printf(“y是否大于等于x:%d\n”, y>=x ); //y是否大于等于x 输出1
printf(“y是否小于z:%d\n”, y<z ); //y是否小于z 输出1
printf(“z是否小于等于x:%d\n”,z<=x); //z是否小于等于x 输出0
printf(“z是否等于x+y:%d\n”, z==(x+y)); //z是否等于x+y 输出1
return 0;
※ 逻辑运算符
逻辑运算的值也是有两种分别为“真”和“假”,C语言中用整型的1和0来表示。
1) 与运算(&&)
参与运算的两个变量都为真时,结果才为真,否则为假。例如:5>=5 && 7>5 ,运算结果为真;
2) 或运算(||)
参与运算的两个变量只要有一个为真,结果就为真。 两个量都为假时,结果为假。例如:5>=5||5>8,运算结果为真;
3) 非运算(!)
参与运算的变量为真时,结果为假;参与运算量为假时,结果为真。例如:!(5>8),运算结果为真。
某靓女要招募男盆友了,给出的条件是身高不能低于180CM且银行存款不少于100万;
/*逻辑运算符*/
int height = 175; //身高为175cm
double money = 1500000; //银行存款为150万
printf(“是否符合条件:%d\n”,height>=180&&money>=1000000) ;
return 0;
※ 三目运算符( 表达式1 ? 表达式2 : 表达式3; )
* 表达式1 ? 表达式2 : 表达式3;*
执行过程是:
先判断表达式1的值是否为真,如果是真的话执行表达式2;如果是假的话执行表达式3。
例
/*三目运算符*/
//定义小编兜里的钱
double money = 12;
//定义打车回家的费用
double cost = 11.5;
printf(“小编能不能打车回家呢:”);
//输出y小编就打车回家了,输出n小编就不能打车回家
printf(“小编能不能打车回家呢:%c\n”,money>=cost?’y’:’n’);
return 0;
运算符的优先级
循环体
while
详解
注意:
1、while语句中的表达式一般是关系表达或逻辑表达式,当表达式的值为假时不执行循环体,反之则循环体一直执行。
2、一定要记着在循环体中改变循环变量的值,否则会出现死循环(无休止的执行)。
3、循环体如果包括有一个以上的语句,则必须用{}括起来,组成复合语句。
例
/*while循环体*/
int i=1;
int sum=0;
while(i<=100){
sum+=i;
i++;
}
printf(“100以内所有数的和为%d\n”,sum);
do while
详解
例
某公司2014年在职人数为200人,以每年20%增长速度不断扩大招工规模,请使用do-while循环计算从2014开始至哪一年招工规模能够突破1000人。
/*do while循环体*/
int number=200;
int year=2014;
do{
year++;
number*=1.2; //这里是不是应该写点什么?
}while(number<1000&&year<2023); //这里好像缺点什么
printf(“到%d年招工规模突破1000人\n”, year);
for循环
详解
循环过程
第一步:执行表达式1,对循环变量做初始化;
第二步:判断表达式2,若其值为真(非0),则执行for循环体中执行代码块,然后向下执行;若其值为假(0),则结束循环;
第三步:执行表达式3;
第四步:执行for循环中执行代码块后执行第二步;
第五步:循环结束,程序继续向下执行。
例
// 定义变量sum, num
int sum, num;
sum = 0;
for(num = 0;num<=10 ;num++ )//for循环条件与num的变化值
{
sum+=num; //计算每次数字之间的和sum
}
printf(“10以内数的和为:%d”, sum);
return 0;
注意:
i必须在外面申明,这是不是java的语法
int i;
for(i=0;i<length;i++){
printf(“%d”,arr[i]);
}
for循环详解2
在for循环中,表达式1是一个或多个赋值语句,它用来控制变量的初始值;表达式2是一个关系表达式,它决定什么时候退出循环;表达式3是循环变量的步进值,定义控制循环变量每循环一次后按什么方式变化。这三部分之间用分号(;)分开。
使用for语句应该注意:
1、for循环中的“表达式1、2、3”均可可以缺省,但分号(;)不能缺省。
2、省略“表达式1(循环变量赋初值)”,表示不对循环变量赋初始值。如:
3、省略“表达式2(循环条件)”,不做其它处理,循环一直执行(死循环)。如:
4、省略“表达式3(循环变量增量)”,不做其他处理,循环一直执行(死循环)。如:
注:死循环可以使用后面即将讲到的break解决
5、表达式1可以是设置循环变量的初值的赋值表达式,也可以是其他表达式。如:
6、表达式1和表达式3可以是一个简单表达式也可以是多个表达式以逗号分割。如:
运行结果为
7、表达式2一般是关系表达式或逻辑表达式,但也可是数值表达式或字符表达式,只要其值非零,就执行循环体。
8、各表达式中的变量一定要在for循环之前定义。如:
例:(输出0-1000以内的水仙花数)
/*水仙花数*/
//输出0-1000以内的水仙花数
//定义三位数num,个位数sd,十位数td,百位数hd
int num, sd, td, hd;
//循环所有三位数
for( num=0 ; num<1000; num++)
{
//获取三位数字num百位上的数字
hd =num/100;
//获取三位数字num十位上的数字
td =num%100/10;
//获取三位数字num个位上的数字
sd =num%100%10;
//水仙花数的条件是什么?
if(num==hd*hd*hd+td*td*td+sd*sd*sd&&num>=100)
{
printf(“水仙花数字:%d\n”, num);
}
}
return 0;
循环体的比较
1、在知道循环次数的情况下更适合使用for循环;
2、在不知道循环次数的情况下适合使用while或者do-while循环,如果有可能一次都不循环应考虑使用while循环,如果至少循环一次应考虑使用do-while循环。
但是从本质上讲,while,do-while和for循环之间是可以相互转换的,如:小明被老师罚抄10次computer这个单词,分别用三种语法写:
例
使用循环计算1-2+3-4+5-6+…-100的值?
/* 小伙伴们:
选择你们认为最合理的循环结构完成功能吧 */
int sum = 0; //定义计算结果变量sum
int i = 1; //定义循环数字变量i
int flag = 1; //定义符号状态变量flag
//使用for循环
for(i=1;i<=100;i++)
{
sum+=i*flag;
flag*=-1;
}
printf(“sum=%d\n”,sum);
return 0;
多重嵌套循环(父循环一次,子循环需要全部执行完,直到跳出循环。)
打印金字塔
int i, j, k;
for(i=1; i<5; i++)
{
// 观察每行的空格数量,补全循环条件
for( j=i ; j<5 ;j++)
{
printf(” “); 输出空格
}
//观察每行*号的数量,补全循环条件
for( k=0; k<i*2-1 ; k++)
{
printf(“*”); //每行输出的*号
}
printf(“\n”); //每次循环换行
}
return 0;
输出9*9乘法表
/*打印9*9乘法表*/
// 定义相乘数字i,j以及结果result
int i, j, result;
/*
* 打印9*9乘法表
*/
for(i=9;i>=1;i–){
for(j=1;j<=i;j++){
result=i*j;
printf(“%d*%d=%d “,i,j,result);
}
printf(“\n”);
}
return 0;
break语句
详解
那么循环5次的时候,需要中断不继续循环。在C语言中,可以使用break语句进行该操作,代码实现如下:
运行结果:
使用break语句时注意以下几点:
1、在没有循环结构的情况下,break**不能用在单独的if-else语句中**。
2、在多层循环中,一个break语句只跳出当前循环。
例:打印0-50的所有的素数
/*打印0-50的所有素数*/
int m, n;
for(m=2; m<=50; m++)
{
for(n=2; n<m; n++)
{
if(m%n==0) //什么条件下跳出当前循环
break; //这里应该退出当前循环了
}
if(m == n) //n循环结束后,如果m=n的话就输出m
printf(“%d “, m);
}
return 0;
continue(只能在循环体里面)
跳出一次,继续循环
运行结果:
goto(执行goto语句后,程序将跳转到该标号处并执行其后的语句。)
goto语句通常不用,主要因为它将使程序层次不清,且不易读,但在特定情况下,可以使用goto语句来提高程序的执行速度,所以还是少用为妙。
使用格式
goto 语句标号;
例如:
用goto语句和if语句构成循环求10以内的数之和。
例2:
用goto语句和if语句构成循环求10以内的数之和
/*使用goto,实现跳到某一时刻继续运行*/
int sum = 0;
int i;
for(i=1; i<=10; i++)
{
printf(“%d\n”, i);
if(i==3) //是不是少了一个判断呢?
goto BREAK; //在这里使用goto语句
}
BREAK:printf(“结束for循环了….”); //请选择合适位置添加标识符
return 0;
switch
详解
其执行顺序如下图:
在使用switch语句时还应注意以下几点:
1、在case后的各常量表达式的值不能相同,否则会出现错误。
2、在case子句后如果没有break;会一直往后执行一直到遇到break;才会跳出switch语句。
3、switch后面的表达式语句只能是整型或者字符类型。
4、在case后,允许有多个语句,可以不用{}括起来。
5、各case和default子句的先后顺序可以变动,而不会影响程序执行结果。
6、default子句可以省略不用。
自创函数(方法函数)
详解
注意:
注意:
1、[]包含的内容可以省略,数据类型说明省略,默认是int类型函数;参数省略表示该函数是无参函数,参数不省略表示该函数是有参函数;
2、函数名称遵循标识符命名规范;
3、自定义函数尽量放在main函数之前,如果要放在main函数后面的话,需要在main函数之前先声明自定义函数,声明格式为:[数据类型说明] 函数名称([参数]);
例
如果在main函数之后,申明了自定义函数,在前面需要定义函数名
#include<stdio.h>
void find();//在main函数之前定义函数
int main()
{
find();
return 0;
}
void find()
{
printf(“自定义函数如果在main函数后面定义,就需要在main函数前面先定义”);
}
有参和无参函数
形参和实参
函数的参数分为形参和实参两种,形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数,实参是在调用时传递该函数的参数。
形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。
实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值等办法使实参获得确定值。
在参数传递时,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配”的错误。
例如:以下函数multiDouble(int x)实现把x乘以2再返回给函数调用处。
递归函数(自己调用自己)
例(算n的阶乘)
/*递归算n的阶乘*/
int factorial(int n){
int result;
if(n<0){
printf(“输入错误!\n”);
return 0;
}else if(n==0||n==1){
result=1;
}else{
result=factorial(n-1)*n;//调用自身
}
return result;
}
int main(){
int n=5;
printf(“%d的阶乘=%d”,n,factorial(n));
return 0;
}
例(小明第一天记1个单词,第二天记2个单词,第10天时,学会了多少个单词)注意:记和学,是两码是事
第一天:记了1个;
第二天:记了2个+第1天记1个=3个;
第三天:记了3个+第1天记1个+第二天记2个=6个;
第四天:记了4个+第1天记1个+第二天记2个+第三天记3个=10个;
…………………………………………
第十天:记了10个+第1天记1个…………=55个;
/* 定义获取单词数量的函数 */
int getWordNumber(int n)
{
if(n == 1)
{
return 1; //第一天只会1个单词
}
else{
return getWordNumber(n-1)+n; //到第天会的单词数量
}
}
int main()
{
int num = getWordNumber(10); //获取会了的单词数量
printf(“小明第10天记了:%d个单词。\n”, num);
return 0;
}
由阶乘算法,详解递归
递归函数特点:
每一级函数调用时都有自己的变量,但是函数代码并不会得到复制,如计算5的阶乘时每递推一次变量都不同;
每次调用都会有一次返回,如计算5的阶乘时每递推一次都返回进行下一次;
递归函数中,位于递归调用前的语句和各级被调用函数具有相同的执行顺序;
递归函数中,位于递归调用后的语句的执行顺序和各个被调用函数的顺序相反;
递归函数中必须有终止语句。
一句话总结递归:自我调用且有完成状态。
例:
猴子第一天摘下N个桃子,当时就吃了一半,还不过瘾,就又多吃了一个。第二天又将剩下的桃子吃掉一半,又多吃了一个。以后每天都吃前一天剩下的一半零一个。到第10天在想吃的时候就剩一个桃子了,问第一天共摘下来多少个桃子?并反向打印每天所剩桃子数。
int getPeachNumber(int n)
{
int num; //定义所剩桃子数
if(n==10)
{
return 1; //递归结束条件
}
else
{
num = (getPeachNumber(n+1)+1)*2; //这里是不应该用递归呢?
printf(“第%d天所剩桃子%d个\n”, n, num); //天数,所剩桃子个数
}
return num;
}
int main()
{
int num = getPeachNumber(1);
printf(“猴子第一天摘了:%d个桃子。\n”, num);
return 0;
}
例:
/*
有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁。
问第4个人岁数,他说比第3个人大2岁。
问第3个人,又说比第2人大两岁。问第2个人,说比第1个人大两岁。
最后 问第1个人,他说是10岁。请问第5个人多大?
*/
int getAgeOfFifth(int n){
if(n==1){
return 10;
}else{
return getAgeOfFifth(n-1)+2;
}
}
int main()
{
printf(“第5个人的年龄是%d岁”, getAgeOfFifth(5));
return 0;
}
局部与全局
局部变量
局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内, 离开该函数后再使用这种变量是非法的。在复合语句中也可定义变量,其作用域只在复合语句范围内。
全局变量
全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。
局部变量和全局可以标识符(变量名)可以相同,调用时,优先调用局部变量
例
例
/*局部与全局变量*/
int x = 77;
void fn1()
{
printf(“fn1(): x=%d\n”, x);
}
int main()
{
int x = 10;
if(x>0)
{
int x=10;
x = 100;
x /= 2;
printf(“if语句内, x=%d\n”, x);
}
printf(“main方法内, x=%d\n”, x);
fn1();
return 0;
}
存储类别
C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式。
静态存储方式:是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。
动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数;自动变量;函数调用时的现场保护和返回地址等。
自动(auto)
用关键字auto定义的变量为自动变量,auto可以省略,auto不写则隐含定为“自动存储类别”,属于动态存储方式。
静态(static)
用static修饰的为静态变量,如果定义在函数内部的,称之为静态局部变量;如果定义在函数外部,称之为静态外部变量。如下为静态局部变量
注意:静态局部变量属于静态存储类别,在静态存储区内分配存储单元,在程序整个运行期间都不释放;静态局部变量在编译时赋初值,即只赋初值一次;如果在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。
寄存器(register)
为了提高效率,C语言允许将局部变量得值放在CPU中的寄存器中,这种变量叫“寄存器变量”,用关键字register作声明。
注意:只有局部自动变量和形式参数可以作为寄存器变量;一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量;局部静态变量不能定义为寄存器变量
外部(extern)
一般程序执行顺序由上到下,上图,按正常逻辑来说,外部变量x是访问不到的,但是使用关键字extern申明的变量x,可以访问到外部变量x
内部外部函数
内部函数(static [数据类型] [函数名](形式参数);)
如果定义static叫做内部函数,只能被本文件调用,不能被其他源文件调用
外部函数(extern [数据类型] [函数名](形式参数);)
有extern修饰的,可以被其他源文件访问
例
数组
数组的定义
数据类型 数组名称[长度];
数组的初始化
数据类型 数组名称[长度n] = {元素1,元素2…元素n};
定义数组array:
int array[3]={1,2,3};
如果采用第一种初始化方式,元素个数小于数组的长度时,多余的数组元素初始化为0;
2、 数据类型 数组名称[] = {元素1,元素2…元素n};
3、 数据类型 数组名称[长度n];
数组名称[0] = 元素1;
数组名称[1] = 元素2;
数组名称[n] = 元素n+1;
获取数据组对应的数据
获取数组元素时: 数组名称[元素所对应下标];
例:初始化一个数组 int arr[3] = {1,2,3}; 那么arr[0]就是元素1。
数组长度(特殊方法获取)
int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
int length=sizeof(arr)/sizeof(arr[0]);//sizeof 是用来获取某个变量所占运行内存多少,所以总内存除以单个元素内存就是数组的元素个数,即长度
注意:
在声明数组后没有进行初始化的时候,静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。
例
//第一种形式
int arrFirst[3] = {1,2,3};
//第二种形式
int arrSecond[] = {3,4,5};
//第三种形式
int arrThird[3];
//给arrThird数组每个元素初始化
arrThird[0]=6;
arrThird[1]=7;
arrThird[2]=8;
//输出第一个数组中的第二个元素
printf(“%d\n”, arrFirst[1]);
//输出第二个数组中的第二个元素
printf(“%d\n”, arrSecond[1]);
//输出第三个数组中的第二个元素
printf(“%d\n”, arrThird[1]);
return 0;
多维数组
多维数组的初始化
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n] = {{值1,..,值n},{值1,..,值n},…,{值1,..,值n}};
例:int arr[][]={{0,1,2},{0,1,2},{0,1,2}};
初始化数组声明必须指定列的维数。因为系统会根据数组中元素的总个数来分配空间,当知道元素总个数以及列的维数后,会直接计算出行的维数;
数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n]; 数组名称[下标1][下标2]…[下标n] = 值;
例:int arr[3][3]={{0,1,2},{0,1,2},{0,1,2}};
初始化时数组声明必须同时指定行和列的维数。
注意:二维数组定义的时候,可以不指定行的数量,但是必须指定列的数量。
求对角线元素之和int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
0 | 1 | 2 |
---|---|---|
0 | 1 | 2 |
1 | 4 | 5 |
2 | 7 | 8 |
#include <stdio.h>
int main()
{
int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};
int i,j;
int sum=0;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++){
if(i%2==0&&j%2==0){
sum+=arr[i][j];
}
}
}
sum+=arr[1][1];
printf(“对角线元素之和是:%d\n”,sum);
return 0;
}
字符串(C语言中没有字符串,只有字符数组)
定义字符串的方式(定义字符数组的方式)
char 字符串名称[长度] = “字符串值”;
例:char string[] = “我在慕课网上学习IT技能!”;
[]中的长度是可以省略不写的;
char 字符串名称[长度] = {‘字符1’,’字符2’,…,’字符n’,’\0’};
例:char string[]={“a”,”b”,”\0”}
[]中的长度是可以省略不写的;
采用第2种方式的时候最后一个元素必须是’\0’,’\0’表示字符串的结束标志;
注意:这种申明方法不能在数组中不能写中文。
输出方式
在输出字符串的时候要使用:printf(“%s”,字符数组名字);或者puts(字符数组名字);
例
/* 定义say函数 */
void say(char string[]) //数组参数应该怎么写呢?
{
printf(“%s\n”,string); //打印字符串
}
int main()
{
//定义字符串数组
char string[] = “我在学习IT技能!”;
say(string); //调用say函数输出字符串
return 0;
}
调用系统字符串函数<string.h>
strlen(s) 获取字符串的长度,在字符串长度中是不包括‘\0’而且汉字和字母的长度是不一样的。
strcmp()在比较的时候会把字符串先转换成ASCII码再进行比较,返回的结果为0表示s1和s2的ASCII码相等,返回结果为1表示s1比s2的ASCII码大,返回结果为-1表示s1比s2的ASCII码小
strcpy()拷贝之后会覆盖原来字符串且不能对字符串常量进行拷贝
strcat()字符串拼接,在使用时s1与s2指的内存空间不能重叠,且s1要有足够的空间来容纳要复制的字符串
例
#include <stdio.h>
#include <string.h>
int main()
{
char s1[100]=”“;
char s2[]=”我爱,”;
char s3[]=”慕课网”;
/*在以下补全代码*/
strcpy(s1,s2);
strcat(s1,s3);
printf(“%s\n”,s1);// 我爱,慕课网
return 0;
}
c语言指针
概述
一般把内存中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不一样,如int占用4个字节,char占用1个字节。
指针
一个变量的地址称为该变量的指针
指针变量
如果有一个变量用来专门存放另一个变量的地址(指针),称为他为指针变量
指针变量的申明(指针的基类型,表示此指针可以指向什么样类型的变量)
[基类型] * [标识符]
例:
int * p; //申明指针变量
指针操作命令(&[变量名])
int i , *p; p=&i ; 将变量i的地址值存储到p中
注意:两种输出方式
#include<stdio.h>
int main(){
int n=3;
//指针的
int * i_pointer=&n;//&[变量名],表示操作指针命令,取n的指针
printf(“%d\n”,i_pointer);//直接输出指针的值,输出1703740
printf(“%d\n”,* i_pointer);//*i_pointer,表示取指针,所以指向的变量的值,输出为3
return 0 ;
}
指针变量可作为函数参数
操作指针形参不会影响实参
例:
/*
*测试指针作为函数参数时,值的变化
*
*测试结果,函数内针对形参的赋值,与传入时实参的值没有影响
*/
void swap(int * p1,int*p2);//放在程序前面,先申明
int a,b;
int* pointer_a,*pointer_b;
scanf(“%d%d”,&a,&b);
printf(“当前a=%d,b=%d\n”,a,b);//输入5和6
pointer_a=&a;
pointer_b=&b;
if(a<b){
swap(pointer_a,pointer_b);
}
printf(“判断最大最小,排序是max=%d,min=%d\n”,*pointer_a,*pointer_b);//输出max=5,min=6,明显跟自己的思路不一样,由此得出结论
printf(“得出结果,不符合需求,由此可见,函数内对指针形参的操作,不会影响实参的值\n”);
return 0 ;
}
void swap(int*p1,int*p2){
int*p;
p=p1;
p1=p2;
p2=p;
}
通过指针引用数组
指针指向数组首元素
有两种表示方法:
1、p=&a[0]
int a[10]{0,1,2,3,4,5,6,7,8,9}
int*p;
p=&a[0];
p=a;
int a[10]{0,1,2,3,4,5,6,7,8,9}
int*p;
p=a;//这样的赋值方式:是将直接将a的首个元素指针进行赋值
输出所有数组元素
/**
*利用指针,打印数组的元素
*/
int arr[]={1,3,5,7,9,11,13,15,17,19};
int length=sizeof(arr)/sizeof(arr[0]);//数组长度,sizeof是获取内存大小
//方法1:
int i;
int*i_pointer;
printf(“方法1:通过\”i_pointer=&arr[i];\”获取数组元素的指针\n”);
for(i=0;i<length;i++){
i_pointer=&arr[i];
printf(“%d\n”,*i_pointer);
}
printf(“方法2:通过\”*(arr+i)\”输出元素的值\n”);
//方法2:
for(i=0;i<length;i++){
//i_pointer=a+i;
printf(“%d\n”,*(arr+i));
}
//方法3:
printf(“方法3:通过\”for(i_pointer=arr;i_pointer<arr+10;i_pointer++)\”初始化指针为arr首个元素的指针,循环获取指针指向的元素值\n”);
for(i_pointer=arr;i_pointer<arr+10;i_pointer++){
//i_pointer=a+i;
printf(“%d\n”,*i_pointer);
}
指针可以带下标p[0]等价a[0]
int a[]={1,3,5,7,9,11,13,15,17,19};
printf(“使用\”a[0]\”,输出的结果:%d\n”,a[0]);
int*p=a;
printf(“使用\”p[0]\”,输出的结果:%d\n”,p[0]);
使用指针需要注意的问题
操作数组并不会越界、报错
array为10,*(array+10)
/**
*指针不会越界报错
*/
int arr[]={0,1,2,3,4,5,6,7,8,9};
printf(“输出\”*(arr+10)\”:%d\n”,*(arr+10));//指针操作数组,这样是不会越界报错,越出的指针(地址),会自动查找已经分配好的内存单元
“p++;*p”和“*p++”
由于”*”和“++”的运算优先级,属于同一级,所以“*p++”,是先引用p所指向的值,然后再++