在学习C语言之前,我们应该思考我们为什么要学习C语言呢?C语言又有什么用呢?
如果一味的苦学和研究,很容易让我们陷入“一叶账目不见泰山”的地步,所以在学习C语言之前要知道C语言发展历程和C语言的优势和劣势。
C语言在1973年的贝尔实验室诞生,是一门通用的计算机编程语言。C语言远胜于汇编语言的可移植性,有着良好的跨平台的特性。C语言的诞生目的就是为了提供一种能以简易的方式编译、处理低级存储器、而且仅产生少量的机器码以及不需要任何运行环境支持就可以能运行的编程语言。
在1989年,ANSI发布了第一个完整的C语言标准,简称C89,目前习惯称之为ANSI C。1990年又被国际标准组织ISO一字不改的采纳,命名为ISO/IEC 9899,简称C90。1999年,ISO做了一些修正和完善,发布新的C语言标准,简称C99。2011年发布新的标准,简称C11。
C语言作为最底层的基础开发语言,它的地位从来没有被撼动过,在嵌入式领域依然是主流语言。新的语言不断出现,但是C语言作为基础,基本不可能被替代的,现在的Linux内核经过无数迭代发展,C语言依然是主流。
要学习一门语言,要先知道一门语言的优缺点,方便日后我们在做项目等方面可以选择一个最适合的语言,所以一个语言的优势和劣势是我们所必须知道的。
C语言就是为了可移植性而产生的,它可以确保在不同的软硬件平台上运行。
C语言可以像汇编语言一样可以直接访问物理地址,对计算机硬件的3种工作单元(位,字节,地址)来进行操作。兼备高级语言和低级语言的功能。
C语言是编译型语言,生成的代码质量较高。
可以实现各种复杂的数据结构运算,各种运算符也使得其表达式多样化,可以实现其他高级语言难以实现的运算。而且C语言程序设计自由的较大,且语法灵活,允许其编写者进行最大程度的发挥
C语言经过多年的发展,提供了大量的库函数。
我们不能指望一门语言解决所有问题,每个语言都不是十全十美的,就像人一样,都有自己的优缺点,我们也要学习其他语言,熟悉其他语言的优缺点,在日后写程序时选择最适合的语言。
#include
int main()
{
printf("你好");
return 0;
}
这就是一个简单的程序了。
运行结果如下:
1.#include
2.int main()是主函数,一个程序有且仅有一个主函数,程序的执行也是从main函数开始的。main函数下会跟一对{},代码的内容在花括号内完成。
3.printf表示要在屏幕上输出的结果,把要输出的放入(" ")里面。
4.return 0是这个函数返回值为零,return代表这个函数执行结束。
每个语句后都要有;,英文的分号,当然也有例外。这个后面会介绍。
数据类型有常量和变量之分;常量顾名思义就是程序执行过程中不能改变的;变量是程序执行过程中可以改变的。
变量的定义指定一个数据类型,并包含该类型的的一个或多个变量列表。
创建格式:数据类型 变量名;
数据类型:基础类型,构造类型,指针类型。
初识C语言,我们先了解基础类型,其他之后在学习。
字符类型: char
短整型: short
整形: int
长整型: long
长长整型: long long
单精度浮点型: float
双精度浮点型: double
我们可以给变量赋值:
#include
int main()
{
int a;//数据类型 变量名;
a = 10;//变量名 = 赋值表达式
int b = 100;//二者也可以合二为一。数据类型 变量名 = 赋值表达式
printf("%d %d", a, b);//%d是打印整形
return 0;
}
了解的基础类型,我们还要知道每个类型所占的空间大小。
#include
int main()
{
//sizeof是操作符,不是函数,作用是计算类型或者变量所占空间的大小
printf("%d", sizeof(char));
printf("%d", sizeof(short));
printf("%d", sizeof(int));
printf("%d", sizeof(long));
printf("%d", sizeof(long long));
printf("%d", sizeof(float));
printf("%d", sizeof(double));
return 0;
}
运行结果如下:
我们发现这并不能让我们清楚的知道每一个值,所有C语言加入了转义符。我们先修改一下上面的程序,在来介绍转义符。
改写如下:
#include
int main()
{
//sizeof是操作符,不是函数,作用是计算类型或者变量所占空间的大小
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof(long));
printf("%d\n", sizeof(long long));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
return 0;
}
运行结果如下:
我们发现在printf里面加入了\n是我们更加容易观察数据,这就是转义符的作用。
下面是一些常见的转义符:
转义字符 | 转义字符的意义 |
---|---|
\a | 鸣铃 |
\b | 退格 |
\t | 制表符 |
\n | 换行 |
\f | 走页换纸 |
\r | 回车 |
\" | 双引号符 |
\ ’ | 单引号符 |
\\ | 反斜杠符 |
\ddd | 1~3位八进制所代表的字符 |
\xhh | 1~2位十六进制所代表的字符 |
我们上个就是使用了\n换行符,有兴趣的可以自己动手尝试一下其他换行符。一定要勤动手哦。
我们先通过一个简单的程序来看看变量的使用。
int main()
{
int num1 = 0;//操作的第一个数,并初始化为0;
int num2 = 0;//操作的第二个数,并初始化为0;
int sum = 0;
scanf("%d %d", &num1, &num2);
sum = num1 + num2;
printf("sum=%d", sum);
return 0;
}
从代码中,我们创建了两个整形为num1和num2并且对他们进行了初始化。创建的整形sum用来存放两个数相加得到的数据,scanf(“%d %d”, &num1, &num2)用来获得从键盘上输入的数据,&为取地址符,使获得的数据可以对变量进行正确的赋值。最后打印出sum的值。
局部变量:属于某个{},在这个{}外不可以使用。
全局变量:在函数之外定义,对整个代码有效。如果在{}内有和全局变量名字一样的局部变量,则局部变量优先。
int num = 10;//全局变量
int main()
{
int num = 100;//局部变量
{
int num = 1000;//局部变量
printf("num = %d\n", num);
}
printf("num = %d\n", num);
return 0;
}
从上面的案例可以看出局部变量优先。以最近的值作为名字相同变量的值。
作用域:作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。
变量的生命周期:变量的创建到变量的销毁之间的时间段。
局部变量为执行到局部变量定义语句才会分配空间创建。离开{}自动释放。局部变量的生命周期就是从创建开始,到出作用域结束。
全局变量在编译阶段就分配了空间,只有程序结束后才释放。全局变量的生命周期为整个程序的生命周期。
常量可以分为字面值常量和const修饰的常变量。字面常量为一个值。const修饰的常变量为创建后值就不可以再改变。
注释对于程序来讲是一个必不可少的组成部分,它是程序开发者向程序读者传递思想的重要渠道。
C语言有2种程序注释方法
行注释:以//开始,以换行符结束的单行注释。
块注释:以/*开始,以*/结束的块注释。块注释的缺陷就是不可以嵌套注释。
对比两张图片可以看出块注释的缺陷。
一元运算符:只有一个操作数,主要包括正号运算符(+),负号运算符(-),递增运算符(++),递减运算符(- -)。
二元运算符:含有二个操作数,加(+),减(-),乘(*),除(/),取余(%)(取余的二个操作数不可以为小数)。
运算符可以任意结合,且一般来说一元运算符的优先级高于二元运算符。但在具体运算时同样遵循数学中的“先乘除在加减的原则”。
int main()
{
int a = -1;//负号运算符
int b = +10;//正号运算符
int c, d, e, f, g;
c = a + b; //加
d = a - b;//减
e = a * b;//乘
f = b / c;//除
g= b % c;//取余,b/c的余数
printf("a=%d b=%d c=%d d=%d e=%d f=%d g=%d \n", a, b, c, d, e, f, g);
a = 9;
b = 9;
printf("%d %d\n", ++a, a);//前置递增运算符,先进行加一,再使用该值。
printf("%d %d\n", b++, b);//后置递增运算符,先使用该值,再进行加一。
c = b * a++;//一元运算符高于二元运算符,运算为b与a++求积把值赋给c,而a++为后置递增运算符,所以结果为10*10,同时a自身增加1.
d = b * ++a;//运算为b与a++求积把值赋给c,而++a为前置递增运算符,所以结果为10*12,a自身先增加1再进行乘积.
printf("%d\n", c);
printf("%d\n", d);
return 0;
}
基本赋值运算符:=(将右侧的常量或表达式值赋给左侧的变量)
复合赋值运算符:+=(加赋值),-=(减赋值),*=(乘赋值),/=(除赋值),%=(取余赋值),<<=(左移赋值),>>=(右移赋值),&=(按位与赋值),|=(按位或赋值),^=(按位异或赋值)。
int main()
{
int a = 10;//基本赋值运算符
a += 2;//复合赋值运算符,等价于a=a+2
printf("%d\n", a);
a -= 2;//等价于a=a-2
printf("%d\n", a);
a *= 2;//等价于a=a*2
printf("%d\n", a);
return 0;
}
其他的复合赋值运算符和上面的同理。
赋值运算符的优先级很低,仅仅高于逗号运算符。
比如:
a+=1+2*3;//等价于a=a+(1+2*3);只有做完算数运算符后才能进行赋值操作。
在逻辑表达式的值为逻辑值,即为布尔型(bool),只有0为逻辑假值(false),一切非0的值均可判断为逻辑真值(true)。通常用1来表示逻辑真值,0来表示逻辑假值。
一元运算符:!(逻辑非)当操作数为真时,取非后为假,反之操作数为假时,取非后为真。
二元运算符:&&(逻辑与)两个同为真才为真,||(逻辑或)两个同为假才为假。
int main()
{
int a = 0, b = 1;
bool c, d, e, f, g, h;
c = !a;
printf("c=%d\n", c);
d = a && b;
printf("d=%d\n", d);
e = a || b;
printf("e=%d\n", e);
f = !a || ++b && a--;//等价于(!a)||((++b)&&(a--))
printf("f=%d\n", f);//可以看出逻辑非(!)>算数运算符>逻辑与(&&)和逻辑或(||)>赋值(=)。
a = 1; b = 1;
g = a || b++;//发生短路,当a为真时,右侧的b++就不在执行
printf("g=%d a=%d b=%d\n", g, a, b);
a = 0, b = 1;
h = a && b++;//发生短路,当a为假时,右侧的b++就不在执行
printf("h=%d a=%d b=%d\n", h, a, b);
return 0;
}
使用逻辑运算符要注意短路的情况,避免在逻辑运算符中进行数值变化。
左移运算符(<<):a<
左移运算符(>>):a>>m(a和m必须为整形表达式,且m>=0)表示将整数a按二进制位向右移动m位,低位移出后,高位补0。
int main()
{
int a = 3;
a <<= 1;
/* 3的二进制为00000011
左移一位为 00000110
位移后的结果为6
*/
printf("a=%d\n", a);
a >>= 2;
/* 6的二进制为00000110
右移两位为 00000001
位移后的结果为1
*/
printf("a=%d\n", a);
return 0;
}
注意:使用位移运算符时需关注操作数运算后是否超过本身的定义范围,如果超过范围会被截断。
关系运算符有>(大于),>=(大于等于),<(小于),<=(小于等于),==(等于),!=(不等于)
前四个的优先级高于后两个。其结果为布尔型(bool)
如:
int a=1,b=2;
a>b:逻辑为假,其值为0
1.按位与:二元运算符,如果两个数的二进制相同的位都为1,则该位的结果为1,否则为0。
2.按位或:二元运算符,如果两个数的二进制相同的位中任意一个为1,则该位的结果为1,否则为0。
3.按位异或:二元运算符,如果两个数的二进制相同的位相同则0,否则为1。
4.按位取反:一元运算符,用来对一个二进制数按位取反,即将0变为1,把1变为0。
int main()
{
int a = 1, b = 3;
int c, d, e, f;
/*
a 0001
b 0011
c 0001
*/
c = a & b;//相同的位都为1,结果为1
/*
a 0001
b 0011
d 0011
*/
d = a | b;//相同的位都为0,结果为0
/*
a 0001
b 0011
e 0010
*/
e = a ^ b;//相同则0,相异为1
/*
a 0000 0000 0000 0000 0000 0000 0000 0001
~a 1111 1111 1111 1111 1111 1111 1111 1110 补码
~a 1111 1111 1111 1111 1111 1111 1111 1101 反码 = 补码 - 1
~a 1000 0000 0000 0000 0000 0000 0000 0010 原码 = 反码符号位不变,其他位按位取反。
*/
f = ~a;
printf("c=%d\nd=%d\ne=%d\nf=%d\n", c, d, e, f);
return 0;
}
按位异或的一个常用场景是不创建临时变量的情况下交换两个变量的值
int main()
{
int a = 1;
int b = 2;
printf("交换前:a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后:a=%d b=%d\n", a, b);
return 0;
}
条件运算符的格式为条件?表达式1:表达式2,是一个三元运算符。
其中的的条件必须是标量类型,也就是算数类型或指针类型。
int main()
{
int a = 10;
int b = 20;
int c = a > b ? a : b;//条件成立则为表达式1,不成立则执行表达式2.
printf("c=%d", c);
return 0;
}
逗号表达式为从左到右逐个计算,逗号表达式为一个整体,它的值为最后一个表达式的值。
int main()
{
int a = 10;
int b = 20;
int c = (a + b, a++, b++, b - a);
printf("有括号:a=%d b=%d c=%d\n", a, b, c);
a = 10;
b = 20;
c = a + b, a++, b++, b - a;
printf("无括号:a=%d b=%d c=%d\n", a, b, c);
return 0;
}
运算符要注意优先级,优先级相同的按照结合顺序计算,大多为从左向右,也有从右向左计算的。
顺序流就是我们常说的顺序结构,是C语言最简单的流结构,是按照代码一行一行的执行。
条件分支流就是我们所说的选则结构,主要有两种,分别为if结构和switch结构
单个if:如果条件为真,就执行后面语句,否则不执行。
int main()
{
int a = 10;
int b = 20;
int c = 0;
if (a > b)//条件成立则进入{}执行{}里面的语句.
{
c = a + b;
}
printf("c=%d", c);
return 0;
}
if-else:如果条件为真,就执行if后语句块,否则执行else后语句块。
int main()
{
int a = 10;
int b = 20;
int c = 0;
if (a > b)//条件为假
{
c = a + b;
}
else//执行else的语句块
{
c = a * b;
}
printf("c=%d", c);
return 0;
}
if-else if-else:先判段条件1,成立执行条件1的语句,其他不执行;若1不成立,则检查2是否成立,如果3成立,则证明1,2不成立。整个过程只有一个区域块语句被执行。
int main()
{
int age = 60;
if (age < 18)//age=60,不成立。
{
printf("未成年\n");
}
else if (age < 30)//age=60,不成立。
{
printf("青年\n");
}
else if (age < 60)//age=60,不成立。
{
printf("壮年\n");
}
else
{
printf("老年\n");
}
return 0;
}
switch结构:该结构把整形表达式与常量表达式进行比较,若相等则执行后面所有语句,直到遇到break语句才会跳出switch结构,若条件都不满足,可以执行default语句。
/*switch语句的结构
switch (整形表达式)
{
case 常量表达式1:语句1;
break;
case 常量表达式2:语句2;
break;
case 常量表达式3:语句3;
break;
default:语句4;
break;
}
*/
int main()
{
char ch = 'A';//存放成绩的字符
switch (ch)
{
case 'A':printf("你的成绩为优等\n");
break;
case 'B':printf("你的成绩为良好\n");
break;
case 'C':printf("你的成绩为不及格\n");
break;
default:printf("你的成绩有误\n");
break;
}
return 0;
}
int main()
{
char ch = 'A';//存放成绩的字符
switch (ch)
{
case 'A':printf("你的成绩为优等\n");
case 'B':printf("你的成绩为良好\n");
case 'C':printf("你的成绩为不及格\n");
default:printf("你的成绩有误\n");
}
return 0;
}
缺少break;匹配的位置后面所有语句都会被执行,直到找到一个break;或者switch结构执行到结束。
循环控制流就是我们所说的循环结构,常用的有for,while和do while。
for循环:
/*
for循环基本语法:
//先执行初始表达式,再判断逻辑,逻辑为真则执行循环,为假则不执行,
//然后执行循环体,最后执行过程表达式,然后再进行判断逻辑。循环此过程。
for(初始表达式;逻辑表达式;过程表达式)
{
循环体
}
*/
int main()
{
int sum = 0;
int i;
for (i = 1; i <= 100; i++)//计算前100个自然数的和,
{
sum += i;
}
printf("%d\n", sum);
return 0;
}
注意:for循环的初始表达式,逻辑表达式,过程表达式都可以省略。逻辑表达式没有的话会进入死循环。初始表达式,逻辑表达式不写也要加上分号;初始表达式,过程表达式可以写任意条语句,但要用逗号隔开。for循环后括号后不加分号。
while循环:
/*
while循环基本语法:
//判断逻辑,逻辑为真则执行循环,为假则不执行,
//执行循环体再进行判断逻辑。循环此过程。
while(逻辑表达式)
{
循环体
}
*/
int main()
{
int sum = 0;
int n = 1;
while (n <= 100)
{
sum += n;
n++;
}
printf("%d\n", sum);
return 0;
}
注意:防止逻辑表达式恒真或恒假,避免进入死循环和不进入循环。while后不加分号。
do-while循环:
/*
do-while循环基本语法:
//执行循环体再进行判断逻辑
do
{
循环体
}while(逻辑表达式);
*/
int main()
{
int sum = 0;
int n = 1;
do
{
sum += n;
n++;
} while (n <= 100);
printf("%d\n", sum);
return 0;
}
注意:do-while和while的区别是前者先进行循环体,在进行逻辑表达式判断,而后者则先进行逻辑表达式判断再执行循环体。while可能一次也不进行循环,而do-while至少执行一次。在do-while中,while括号后要加分号。否则报错。
如果我们想实现每次执行程序都可以获得不同的值进行操作,这是就可以用输入输出函数。
int main()
{
int a = 0;
int b = 0;
printf("请输入要相加的两个数\n");
scanf("%d %d", &a, &b);
printf("%d", a + b);
return 0;
}
程序通过scanf()等待要输入要相加的两个数,然乎在通过printf打印屏幕上。我们还可以用scanf实现我们想要的前n个数相加的结果等操作。
语句是可以嵌套的,循环可以套条件,条件也可以套循环。
//求输入数的前奇数和偶数的和
int main()
{
int a = 0;
scanf("%d", &a);
int num = 0;//存放偶数和
int sum = 0;//存放奇数和
for (int i = 1; i <= a; i++)
{
if (i % 2 == 0)
{
num += i;
}
else
{
sum += i;
}
}
printf("num = %d sum = %d", num, sum);
return 0;
}
条件判断和循环最好加上花括号,否则下面的语句只有第一个语句和条件判断和循环对应。
break:主要用于switch和循环,在switch中结束switch,在循环中则结束本次循环,在循环套switch中如果出现在switch中,则结束switch,不结束循环。
continue:用于循环。结束本次循环,进行下次循环,执行逻辑表达式。
return:退出该函数执行,返回函数调用处。如果是main()函数,则结束程序运行。
学好函数是能熟练使用C语言的第一步。
函数有三大要素:函数名返回类型,函数名和函数参数。
有些函数不需要返回值,此时可以设置为void,而函数名是次函数的唯一标识,函数参数是需要给函数传递的信息。有些也可能不要。
下面的是一个没有返回值,没有参数的函数。
void test()
{
printf("HI\n");
}
extern:extern是C语言的关键字,表明一个函数的声明,声明的时间可以省略。因为函数默认是extern属性。
extern int myadd(int a,int b);
int myadd(int,int);//简化版
被调用的函数在调用函数后面定义时,需要在调用函数的时候或前面加上被调用函数的声明。
注意:函数声明的时候如果没有写类型,默认返回值是int型,这可能导致一些截断或着溢出的问题。
实参:在调用时传递的参数,它们必须为确定的值,可以是常量,变量,表达式等。
形参:是在函数体使用时的参数,实参要与形参的个数,类型一一对应。
传值调用:把实参的值复制给形参,形参的改变不改变实参。
传址调用:通过指针的方式传递,形参指向实参的地址,对形参的操作相当于对实参操作。
void myswap1(int a, int b)
{
int tmp = a;
a = b;
b = tmp;
printf("传值调用中:a=%d,b=%d\n", a, b);
}
void myswap2(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
printf("传址调用中:a=%d,b=%d\n", *a, *b);
}
//被调用的函数在调用函数之前,可以不用声明
int main()
{
int a = 10;
int b = 20;
printf("传值调用前:a=%d,b=%d\n", a, b);
myswap1(a, b);//传值调用
printf("传值调用后:a=%d,b=%d\n", a, b);
printf("\n");
printf("传址调用前:a=%d,b=%d\n", a, b);
myswap2(&a, &b);//传址调用
printf("传址调用后:a=%d,b=%d\n", a, b);
return 0;
}
从结果可以看出传值调用中在函数内部交换了值,但出函数值并未进行交换。
在C语言中函数直接或间接的调用函数本身,则称该函数为递归函数。
递归的条件:1.要有递归公式。2.要有终止条件。
递归的思想逐层分解,把问题进行分解。
int Factorial(int a)
{
if (a <= 1)//终止条件
{
return 1;
}
else
{
return a * Factorial(a - 1);//递归公式
}
}
int main()
{
int a = 10;
printf("%d", Factorial(a));
return 0;
}
从上面可以看出递归和循环有很多相似之处。从理论上来说,所有循环都可以转换为递归,但用递归解决的问题不一定能转换为循环。
使用递归可以简化程序设计。使程序逻辑更加清晰。但递归速度慢,运行效率低,且占存储空间多。对于嵌套层次深的或者对内存要求高的算法,递归可能造成内存崩溃。
本篇了解到函数就可以实现C语言部分的程序,可以开始写一些基本的程序了,想要更深层次的进修,就要学习数组和指针。