C语言常用语法
- 前言
- hello world
- 数据类型
- 变量
- 变量的命名规范
- 变量的分类
- 变量的作用域和生命周期
- 常量
- 字面常量
- const修饰的常变量
- #define定义的标识符常量
- 枚举常量
- 字符串
- 转义字符
- 注释
- 选择语句if
- 循环语句while
- 函数
- 数组
前言
这篇博客旨在帮助零基础的朋友们入门,我会尽可能用通俗易懂的语言讲解C语言最常用的语法。关于这些知识点详细的讲解请阅读我的其它博客。本篇博客可能会涉及一些常识性概念(如计算机的单位、进制转换等),不会做展开,如果你不了解这些知识,请发挥主观能动性,在网络上搜索相关概念并学习。另外,讲解知识时,很难做到面面俱到,比如在讲解某个知识点时,必须要同时讲解另外一个知识点才能讲清楚,而另外一个知识点可能在后面才会提到。所以,如果你阅读时对某一个知识点感到一知半解,请耐心往后面看,说不定就明白了。
hello world
目标:写一个程序,在屏幕上打印hello world。
main函数是程序的入口,程序会从main函数的第一行开始执行。一个工程中有且仅有一个main函数,这是因为:程序必须有入口;若有多个入口,就不知道从哪里开始执行了。
main函数最基础的写法如下:
int main()
{return 0;
}
printf是C语言提供的库函数,用来在屏幕上打印信息。比如打印hello world并换行,只需写:
printf("hello world\n");
这里printf后面跟(),表示函数调用。结尾的;表示语句结束。""引起来的hello world表示字符串。\n表示换行。
使用库函数必须引用头文件,printf对应的头文件是stdio.h,引用头文件的语法如下:
#include <stdio.h>
在屏幕上打印hello world吧。C语言中,以//开头的是注释。
#include <stdio.h>int main()
{// 打印hello world并换行printf("hello world\n");return 0;
}
输出结果:
数据类型
C语言的常用数据类型有:字符数据类型char、短整型short、整型int、长整型long、更长的整型long long、单精度浮点型short、双精度浮点型double。其中,整型可以理解成整数的类型,浮点型可以理解为小数的类型。字符用''
引起来的一个字符表示,比如'w'
。
类型可以用来创建变量,语法是:类型 变量名;
或者类型 变量名 = 初始值;
。如:
char ch1;
char ch2 = 'w';
int age = 10;
double weight = 55.5;
每种类型的大小是多少呢?我们可以用sizeof运算符来计算某一个类型的大小,单位是Byte。由于类型的大小是整数,需要用%d(十进制整数)格式来打印。代码如下:
#include <stdio.h>int main()
{// 打印类型的大小,单位是字节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;
}
输出结果:
MSVC编译环境下,char的大小是1Byte,short的大小是2Byte,int、long和float的大小都是4Byte,long long和double的大小都是8Byte。
为什么同样是整型,有的大,有的小呢?这是因为:long long相比于int,能存储更大的数,自然需要更大的空间。同理,同样是浮点型,double相比于float,可以存储精度更高的小数,自然需要更大的空间。
那为什么long和int都是4Byte呢?这是因为C语言标准规定了:sizeof(long) ≥ sizeof(int)。
变量
能够变化的量叫做变量。我们已经见过用类型定义的变量了,如:
int age = 10;
变量是可以变化的,比如使用赋值语句,把age改为20:
age = 20;
变量的命名规范
- 变量名只能由数字、字母、下划线构成,不能以数字开头,不能太长(最长63个字符)。
- 变量名是区分大小写的,比如age和Age是2个不同的变量。
- 变量名不能使用关键字,比如char(前面提到过,char是一种类型)。
- 变量名最好有意义!
变量的分类
变量分为全局变量和局部变量。在{}外部的变量称为全局变量,在{}内部的变量称为局部变量。如:
int a = 100; // 全局变量int main()
{int b = 20; // 局部变量return 0;
}
全局变量和局部变量可以同名,使用时局部变量优先(就近原则)。
#include <stdio.h>int a = 100;int main()
{int a = 10;printf("%d\n", a);return 0;
}
在打印a时,根据就近原则,会打印局部的a(10),而不是全局的a(100)。
认识一下scanf函数。scanf函数是用来从键盘中输入数据的。使用时和printf很类似,唯一的区别是,变量名前面要加上&。
写一个程序,输入2个整数,在屏幕上打印它们的和。
#include <stdio.h>int main()
{// 定义需要用到的变量int num1 = 0;int num2 = 0;int sum = 0;// 输入2个整数scanf("%d %d", &num1, &num2);// 求和sum = num1 + num2;// 打印结果printf("%d\n", sum);return 0;
}
运行结果:
变量的作用域和生命周期
一个变量能在哪里使用,哪里就是这个变量的作用域。
局部变量的作用域是变量所在的局部范围。换句话说,局部变量在哪个{}内定义,这个变量就只能在这个{}内使用。以下写法会报错:
int main()
{{int a = 10;a = 20; // 可以在这里使用}a = 30; // 不能在这里使用!return 0;
}
全局变量的作用域是整个工程。下面的代码中,新定义了一个函数test,全局变量a既可以在main函数内使用,也可以在test函数内使用。
int a = 10;void test()
{a = 30;
}int main()
{a = 20;test();return 0;
}
全局变量甚至可以跨文件使用。比如在test1.c内定义的全局变量g_val可以在test2.c内使用,但是要提前使用extern声明。extern是用来声明外部符号的,语法是extern 类型 变量名;
。
// test1.c
int g_val = 2024;// test2.c
extern int g_val;int main()
{g_val = 2025;return 0;
}
生命周期指的是变量从创建到销毁的时间段。局部变量的生命周期从进入作用域开始,到出作用域结束。全局变量的生命周期是整个程序的生命周期。
常量
字面常量
直接写出来的常量称为字面常量。如:
10;
3.14;
'w';
const修饰的常变量
一个变量在定义时,如果被const修饰了,称为const修饰的常变量,具有常属性,语法层面上不可被修改。以下写法会报错:
const int a = 10;
a = 20; // 不可被修改
常变量虽然具有常属性,但本质上是变量,不可在初始化数组时作为数组大小(哪怕是C99中的VLA,也只能定义不能初始化 ),这是因为数组初始化的语法是类型 数组名[整型常量] = { 初始值 };
。以下写法会报错:
const int n = 10;
int arr[n] = { 0 };
#define定义的标识符常量
#define可以给字面常量“起名字”,这样定义出来的标识符常量也属于常量,可以用来初始化数组大小。
#define SIZE 10
int arr[SIZE] = { 0 };
枚举常量
枚举类型的基础语法是enum 枚举类型名 { 枚举常量列表 };
,其中枚举常量列表就是一堆枚举常量,中间用,
隔开,下面代码中的MALE、FEMALE和SECRET都是枚举常量,它们的默认值是0、1、2,不可被修改。下面的代码会报错:
enum sex
{MALE,FEMALE,SECRET
};int main()
{MALE = 3; // 不可被修改return 0;
}
字符串
""
引起来的一串字符称为字符串,如"abcdef"
。字符串的结束标志是字符'\0'
,比如字符串"abcdef"
在内存空间中实际上存储的是7个字符,分别是'a', 'b', 'c', 'd', 'e', 'f', '\0'
。每个""
引起来的字符串,结尾都有一个隐藏的'\0'
。
使用printf打印字符串,需要用%s来指定格式,如:
printf("%s\n", "hello world");
本质上,%s会从字符串的起始位置开始,一直打印字符,直到遇到'\0'
才停止打印。
库函数strlen可以计算字符串的长度,本质上是计算从字符串起始位置开始,'\0'
前面有几个字符,长度不包括'\0'
。strlen在头文件string.h中声明。当计算字符串"abc"的长度时,会从字符'a'
开始向后数,直到遇到'\0'
,结果是3。
printf("%d\n", strlen("abc"));
转义字符
转义字符,即转变原来的意思。本来字符'n'
是一个普通的字符,'\n'
却表示换行;本来字符'0'
是一个普通的字符,'\0'
却表示字符串的结束标志。常见的转义字符还有很多。
当我们要打印一个单引号时,不能写:
printf("%c\n", ''');
这3个单引号中,前两个单引号会组成一对,第三个单引号就落单了。正确的写法是:
printf("%c\n", '\'');
在'
前面加一个\,转变它原来的意思,让它成为一个普通的单引号,而不是“一对单引号中的单引号”。同理,若是字符串中有一个",也要使用转义字符:
printf("%s\n", "\"");
'\t'
是水平制表符,可能是2个、4个或8个空格,相当于键盘中的Tab键。如果想打印一串路径:C:\test\test.c,能否这样写?
printf("C:\test\test.c\n");
显然不行,因为\t会被解释成水平制表符。正确的写法是,用\\
表示一个普通的\,而不是转义字符'\t'
中的\。
printf("C:\\test\\test.c\n");
输出结果:
'\ddd'
也是一种常见的转义字符,其中ddd表示1~3个八进制数字。如'\130'
等价于'X'
,因为八进制的130转换为十进制后是1×82+3×81+0×80=64+24=88,对照ASCII码表,88作为ASCII码值对应的字符是'X'
,故'\130'
等价于'X'
。类似的,'\xdd'
也是转义字符,其中dd表示1~2个十六进制数字,这个转义字符等价于把dd这个十六进制数转换成十进制后,作为ASCII码值,对应的字符。
除此之外,还有'\a'
表示蜂鸣,'\b'
表示退格符等等,这里不一一介绍了。
注释
注释是用来解释代码的。
以//开头的是注释,如:
// 这是一个注释
以/*开头,以*/结尾的是注释。如:
/* 这是一个注释 */
这种注释风格不支持嵌套注释,如:
/*/*注释了*/这里没有注释
*/
从第一个/*到第一个*/之间属于注释内容,但是第一个*/到第二个*/之间不属于注释内容。
选择语句if
if语句的语法是:
if (表达式)
{语句列表1
}
else
{语句列表2
}
语句列表可以是一条或多条语句。若表达式为真,则执行语句列表1,否则执行语句列表2。if语句也可以没有else,这样表达式为假时什么都不会发生,语法如下:
if (表达式)
{语句列表
}
C语言中,用0表示假,非0表示真。如:
int flag = 1;
if (flag)
{printf("true\n");
}
else
{printf("false\n");
}
由于1是“非0”,所以会输出true,输出结果:
循环语句while
while语句的语法如下:
while (表达式)
{语句列表
}
和if不同的是,执行完语句列表后,会再次判断表达式是否为真,若为真,会再次执行语句列表,如此循环,直到表达式为假为止。当然,中途也有可能因为break和goto等语句跳出循环。
在屏幕上打印1~10的代码如下:
int i = 1;
while (i <= 10)
{printf("%d ", i);i = i + 1;
}
输出结果:
函数
我们已经见过printf和scanf这样的库函数,能不能自己写一个函数呢?函数定义的语法如下:
返回值类型 函数名(形参列表)
{语句列表
}
使用函数求2个整数的较大值的代码如下:
#include <stdio.h>int max(int x, int y)
{if (x > y){return x;}else{return y;}
}int main()
{int a = 0;int b = 0;scanf("%d %d", &a, &b);int m = max(a, b);printf("%d\n", m);return 0;
}
main函数执行到int m = max(a, b);
时,会调用max函数,把a的值传给x,把b的值传给y,接着根据x和y的大小关系选择返回x和y中的较大值,从而求得a和b的较大值。注意到max函数的返回值类型是int,因为不管是return x;
还是return y;
,返回的都是int类型的变量。
数组
数组是一组相同类型的元素的集合。数组定义的语法是类型 数组名[元素个数];
或类型 数组名[元素个数] = { 初始化元素列表 };
。使用初始化元素列表对数组初始化时,元素个数可以省略。若省略元素个数,会根据初始化元素列表决定数组元素个数。如:
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
arr数组省略了元素个数,会根据{}里有10个元素,决定数组申请40Byte的空间(1个int变量占用4Byte),存储1~10。数组的每一个元素都有下标,下标从0开始。访问数组中的元素的语法是数组名[下标]
,如arr[2]
对应的值是3。那么我们只需要产生0~9的下标,就能访问数组中的所有元素了。
#include <stdio.h>int main()
{int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int i = 0;while (i < 10){printf("%d ", arr[i]);i = i + 1;}printf("\n");return 0;
}
输出结果: