本博文主要是根据上关于C快速入门的内容整理汇总而成
先从一个简单的C语言程序简单分析C语言程序
-
#include <stdio.h>
-
int main()
-
{
-
puts("C语言中文网");
-
return 0;
-
}
第一行是头文件,C语言开发者们编写了很多常用函数,并分门别类的放在了不同的文件,这些文件就称为头文件(header file),类似python语言的中包。较早的C语言标准库包含了15个头文件。
第二,四行是是函数,在C语言中,有的语句使用时不能带括号,有的语句必须带括号。带括号的称为函数(Function)。C语言自带的函数称为库函数(Library Function),如puts(),自己编写的函数称为自定义函数, 如第2-6行代码就是该类。C语言规定,一个程序必须有且只有一个 main 函数。main 被称为主函数,是程序的入口函数,程序运行时从 main 函数开始,直到 main 函数结束(遇到 return 或者执行到函数末尾时,函数才结束)。
C语言中的空白符
空格、制表符、换行符统称为空白符,它们只能占位,没有实际的内容。
-
#include<stdio.h>
-
int main()
-
{
-
puts
-
("C语言中文网");
-
-
puts
-
(
-
"C语言中文网"
-
)
-
;
-
-
puts ("C语言中文网");
-
-
puts ( "C语言中文网" ) ;
-
-
return 0;
-
}
上面的输出结构都是一样的,需要注意的是:字符串中的空格和制表符不会被忽略,它们会被输出到控制台上。
变量和数据类型转换
-
int a=123;
-
a=1000;
-
a=9999
不同于python,C语言中的变量定义必须说明变量的类型,如int表示真是类型。数据是放在内存中的,变量是给这块内存起的名字,有了变量就可以找到并使用这份数据。第一行的意思是在内存中找一块区域,命名为a,用它来存放整数,同时赋值给a。
在C语言中,有多种数据类型,例如:
说 明
|
字符型
|
短整型
|
整型
|
长整型
|
单精度浮点型
|
双精度浮点型
|
无类型
|
数据类型
|
char
|
short
|
int
|
long
|
float
|
double
|
void
|
数据类型转换的一般格式为:
(type_name) expression
-
(float) a; //把a转换为实型
-
(int)(x+y); //把x+y的结果转换为整型
-
(float) 100; //将一个常量转换为实型
C语言自增(++)和自减(--)
值得注意的是,++ 在变量前面和后面是有区别的:
-
++ 在前面叫做前自增(例如 ++a)。前自增先进行自增操作,再进行其他操作。
-
++ 在后面叫做后自增(例如 a++)。后自增先进行其他操作,再进行自增操作。
自减(--)也一样,有前自减和后自减之分。
-
#include <stdio.h>
-
#include <stdlib.h>
-
int main()
-
{
-
int a=10, a1=++a;
-
int b=20, b1=b++;
-
int c=30, c1=--c;
-
int d=40, d1=d--;
-
printf("a=%d, a1=%d\n", a, a1);
-
printf("b=%d, b1=%d\n", b, b1);
-
printf("c=%d, c1=%d\n", c, c1);
-
printf("d=%d, d1=%d\n", d, d1);
-
system("pause");
-
return 0;
-
}
输出结果:
a=11, a1=11
b=21, b1=20
c=29, c1=29
d=39, d1=40
标识符、关键字和注释
定义变量时,我们使用了诸如“a”“abc”“mn123”这样的名字,它们都是程序员自己起的,一般能够表达出变量的作用,这叫做标识符(Identifier)。C语言规定,标识符只能由字母(A~Z, a~z)、数字(0~9)和下划线(_)组成,并且第一个字符必须是字母或下划线。
关键字(Keywords)是由C语言规定的具有特定意义的字符串,通常也称为保留字,例如 int、char、long、float、unsigned 等。我们定义的标识符不能与关键字相同,否则会出现错误。
注释(Comments)可以出现在代码中的任何位置,用来向用户提示或解释程度的意义。程序编译时,会忽略注释,不做任何处理,就好像它不存在一样。
C语言支持单行注释和多行注释:
-
单行注释以//开头,直到本行末尾(不能换行);
-
多行注释以/*开头,以*/结尾,注释内容可以有一行或多行。
C语言输出和输入
在C语言中,有三个函数可以用来在显示器上输出数据:
-
puts():只能输出字符串
-
putchar():只能输出单个字符
-
printf():可以输出各种类型的数据
puts() 函数在输出结束时会自动换行,而 printf() 和 putchar() 不会,需要手动添加换行符\n
printf() 是最灵活、最复杂、最常用的输出函数,完全可以替代 puts() 和 putchar()
-
#include <stdio.h>
-
#include <stdlib.h>
-
int main()
-
{
-
char *str = "c.biancheng.net";
-
int n = 100;
-
char c = 'Z';
-
puts(str);
-
putchar(c);
-
printf("%d", n);
-
putchar(c);
-
system("pause");
-
return 0;
-
}
运行结果:
c.biancheng.net
Z100Z请按任意键继续. . .
程序是人机交互的媒介,有输出必然也有输入。在C语言中,有多个函数可以从键盘获得用户输入:
-
scanf():和 printf() 类似,scanf() 可以输入多种类型的数据。
-
getchar()、getche()、getch():这三个函数都用于输入单个字符。
-
gets():获取一行数据,并作为字符串处理。
scanf() 是最灵活、最复杂、最常用的输入函数,但它不能完全取代其他函数,大家都要有所了解。
-
#include <stdio.h>
-
#include <stdlib.h>
-
int main()
-
{
-
int a, b, c, d;
-
scanf("%d", &a); //输入整数并赋值给变量a
-
scanf("%d", &b); //输入整数并赋值给变量b
-
printf("a+b=%d\n", a+b); //计算a+b的值
-
scanf("%d %d", &c, &d); //输入两个整数并分别赋值给c、d
-
printf("c*d=%d\n", c*d); //计算c*d的值
-
system("pause");
-
return 0;
-
}
运行结果:
12↙
60↙
a+b=72
10 23↙
c*d=230
↙表示按下回车键。
数组和结构体
数组的定义方式:
dataType arrayName[length];
dataType 为数据类型,arrayName 为数组名称,length 为数组长度。例如:
float m[12];
char ch[9];
需要注意的是:
1) 数组中每个元素的数据类型必须相同,对于int a[4];
,每个元素都必须为 int。
2
) 数组是一个整体,它的内存是连续的,下面是int a[4];
的内存示意图:
上面的代码是先定义数组再给数组赋值,我们也可以在定义数组的同时赋值:
int a[4] = {20, 345, 700, 22};
{ }
中的值即为各元素的初值,各值之间用,
间隔。
对数组赋初值需要注意以下几点:
1) 可以只给部分元素赋初值。当{ }
中值的个数少于元素个数时,只给前面部分元素赋值。例如:
int a[10]={12, 19, 22 , 993, 344};
表示只给 a[0]~a[4] 5个元素赋值,而后面5个元素自动赋0值。
当赋值的元素少于数组总体元素的时候,剩余的元素自动初始化为 0:对于short、int、long,就是整数0;对于char,就是字符 '\0';对于float、double,就是小数0.0。
-
#include <stdio.h>
-
int main()
-
{
-
int a[6] = {299, 34, 92, 100};
-
int b[6], i;
-
//从控制台输入数据为每个元素赋值
-
for(i=0; i<6; i++){
-
scanf("%d", &b[i]);
-
}
-
//输出数组元素
-
for(i=0; i<6; i++){
-
printf("%d ", a[i]);
-
}
-
putchar('\n');
-
for(i=0; i<6; i++){
-
printf("%d ", b[i]);
-
}
-
putchar('\n');
-
-
return 0;
-
}
运行结果:
90 100 33 22 568 10
299 34 92 100 0 0
90 100 33 22 568 10
上节讲解的数组可以看作是一行连续的数据,只有一个下标,称为一维数组。在实际问题中有很多数据是二维的或多维的,因此C语言允许构造多维数组。
二维数组的定义
二维数组定义的一般形式是:
dataType arrayName[length1][length2];
其中,dataType 为数据类型,arrayName 为数组名,length1 为第一维下标的长度,length2 为第二维下标的长度。
【示例】一个学习小组有5个人,每个人有三门课的考试成绩。求全组分科的平均成绩和各科总平均成绩。
--
|
张
|
王
|
李
|
赵
|
周
|
Math
|
80
|
61
|
59
|
85
|
76
|
C
|
75
|
65
|
63
|
87
|
77
|
English
|
92
|
71
|
70
|
90
|
85
|
可设一个二维数组a[5][3]存放五个人三门课的成绩。再设一个一维数组v[3]存放所求得各分科平均成绩,设变量average 为全组各科总平均成绩。编程如下:
-
#include <stdio.h>
-
int main(){
-
int i, j; //二维数组下标
-
int sum=0; //当前科目的总成绩
-
int average; //总平均分
-
int v[3]; //各科平均分
-
int a[5][3]; //用来保存每个同学各科成绩的二维数组
-
printf("Input score:\n");
-
for(i=0; i<3; i++){
-
for(j=0; j<5; j++){
-
scanf("%d", &a[j][i]); //输入每个同学的各科成绩
-
sum+=a[j][i]; //计算当前科目的总成绩
-
}
-
v[i]=sum/5; // 当前科目的平均分
-
sum=0;
-
}
-
average =(v[0]+v[1]+v[2])/3;
-
printf("Math: %d\nC Languag: %d\nEnglish: %d\n", v[0], v[1], v[2]);
-
printf("Total:%d\n", average);
-
return 0;
-
}
运行结果:
Input score:
80 61 59 85 76 75 65 63 87 77 92 71 70 90 85↙
Math: 72
C Languag: 73
English: 81
Total:75
二维数组的初始化
二维数组的初始化可以按行分段赋值,也可按行连续赋值。
例如对数组a[5][3],按行分段赋值可写为:
int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85} };
按行连续赋值可写为:
int a[5][3]={80, 75, 92, 61, 65, 71, 59, 63, 70, 85, 87, 90, 76, 77, 85};
这两种赋初值的结果是完全相同的。
数组(Array)是一组具有相同类型的数据的集合。但在实际的编程过程中,我们往往还需要一组类型不同的数据,例如对于学生信息登记表,姓名为字符串,学号为整数,年龄为整数,所在的学习小组为字符,成绩为小数,因为数据类型不同,显然不能用一个数组来存放。
在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:
struct 结构体名{
结构体所包含的变量或数组
};
结构体是一种集合,它里面包含了多个变量或数组,它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)。请看下面的一个例子:
-
struct stu{
-
char *name; //姓名
-
int num; //学号
-
int age; //年龄
-
char group; //所在学习小组
-
float score; //成绩
-
};
stu 为结构体名,它包含了 5 个成员,分别是 name、num、age、group、score。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。
注意大括号后面的分号;不能少,这是一条完整的语句。
结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。
既然结构体是一种数据类型,那么就可以用它来定义变量。例如:
struct stu stu1, stu2;
定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct
不能少。
stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。
你也可以在定义结构体的同时定义结构体变量:
-
struct stu{
-
char *name; //姓名
-
int num; //学号
-
int age; //年龄
-
char group; //所在学习小组
-
float score; //成绩
-
} stu1, stu2;
成员的获取和赋值
结构体和数组类似,也是一组数据的集合,整体使用没有太大的意义。数组使用下标[ ]获取单个元素,结构体使用点号.获取单个成员。获取结构体成员的一般格式为:
结构体变量名.成员名;
通过这种方式可以获取成员的值,也可以给成员赋值:
-
#include <stdio.h>
-
int main(){
-
struct{
-
char *name; //姓名
-
int num; //学号
-
int age; //年龄
-
char group; //所在小组
-
float score; //成绩
-
} stu1;
-
//给结构体成员赋值
-
stu1.name = "Tom";
-
stu1.num = 12;
-
stu1.age = 18;
-
stu1.group = 'A';
-
stu1.score = 136.5;
-
//读取结构体成员的值
-
printf("%s的学号是%d,年龄是%d,在%c组,今年的成绩是%.1f!\n", stu1.name, stu1.num, stu1.age, stu1.group, stu1.score);
-
return 0;
-
}
运行结果:
Tom的学号是12,年龄是18,在A组,今年的成绩是136.5!
除了可以对成员进行逐一赋值,也可以在定义时整体赋值,例如:
-
struct{
-
char *name; //姓名
-
int num; //学号
-
int age; //年龄
-
char group; //所在小组
-
float score; //成绩
-
} stu1, stu2 = { "Tom", 12, 18, 'A', 136.5 };
分支结构和循环结构
if else语句
-
#include <stdio.h>
-
int main()
-
{
-
int age;
-
printf("请输入你的年龄:");
-
scanf("%d", &age);
-
if(age>=18){
-
printf("恭喜,你已经成年,可以使用该软件!\n");
-
}else{
-
printf("抱歉,你还未成年,不宜使用该软件!\n");
-
}
-
return 0;
-
}
if语句的嵌套
-
#include <stdio.h>
-
int main(){
-
int a,b;
-
printf("Input two numbers:");
-
scanf("%d %d",&a,&b);
-
if(a!=b){ //!=表示不等于
-
if(a>b) printf("a>b\n");
-
else printf("a);
-
}else{
-
printf("a=b\n");
-
}
-
return 0;
-
}
for循环语句
for 循环的一般形式为:
for(表达式1; 表达式2; 表达式3){
语句块
}
它的运行过程为:
1) 先执行“表达式1”。
2) 再执行“表达式2”,如果它的值为真(非0),则执行循环体,否则结束循环。
3) 执行完循环体后再执行“表达式3”。
4) 重复执行步骤 2) 和 3),直到“表达式2”的值为假,就结束循环。
上面的步骤中,2) 和 3) 是一次循环,会重复执行,for 语句的主要作用就是不断执行步骤 2) 和 3)。
“表达式1”仅在第一次循环时执行,以后都不会再执行,可以认为这是一个初始化语句。“表达式2”一般是一个关系表达式,决定了是否还要继续下次循环,称为“循环条件”。“表达式3”很多情况下是一个带有自增或自减操作的表达式,以使循环条件逐渐变得“不成立”。
-
#include <stdio.h>
-
int main(){
-
int i, sum=0;
-
for(i=1; i<=100; i++){
-
sum+=i;
-
}
-
printf("%d\n",sum);
-
return 0;
-
}
C语言指针
计算机中所有的数据都必须放在内存中,不同类型的数据占用的字节数不一样,例如 int 占用4个字节,char 占用1个字节。为了正确地访问这些数据,必须为每个字节都编上号码,就像门牌号、身份证号一样,每个字节的编号是唯一的,根据编号可以准确地找到某个字节。
下图是 4G 内存中每个字节的编号(以十六进制表示):
我们将内存中字节的编号称为地址(Address)或指针(Pointer)。地址从 0 开始依次增加,对于 32 位环境,程序能够使用的内存为 4GB,最小的地址为 0,最大的地址为 0XFFFFFFFF。
下面的代码演示了如何输出一个地址:
-
#include <stdio.h>
-
int main(){
-
int a = 100;
-
char str[20] = "c.biancheng.net";
-
printf("%#X, %#X\n", &a, str);
-
return 0;
-
}
%#X表示以十六进制形式输出,并附带前缀0X。a 是一个变量,用来存放整数,需要在前面加&来获得它的地址;str 本身就表示字符串的首地址,不需要加&。
数据和代码都以二进制的形式存储在内存中,计算机无法从格式上区分某块内存到底存储的是数据还是代码。当程序被加载到内存后,操作系统会给不同的内存块指定不同的权限,拥有读取和执行权限的内存块就是代码,而拥有读取和写入权限(也可能只有读取权限)的内存块就是数据。
CPU 访问内存时需要的是地址,而不是变量名和函数名!变量名和函数名只是地址的一种助记符,当源文件被编译和链接成可执行程序后,它们都会被替换成地址。编译和链接过程的一项重要任务就是找到这些名称所对应的地址。
需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址。
定义指针变量
数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量。在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外的一个普通变量或指针变量。
定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*
,格式为:
datatype *name;
或者
datatype *name = value;
*
表示这是一个指针变量,datatype
表示该指针变量所指向的数据的类型 。
-
int a = 100;
-
int *p_a = &a;
在定义指针变量 p_a 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p_a 就指向了 a。值得注意的是,p_a 需要的一个地址,a 前面必须要加取地址符&,否则是不对的。
和普通变量一样,指针变量也可以被多次写入,只要你想,随时都能够改变指针变量的值,请看下面的代码:
-
//定义普通变量
-
float a = 99.5, b = 10.6;
-
char c = '@', d = '#';
-
//定义指针变量
-
float *p1 = &a;
-
char *p2 = &c;
-
//修改指针变量的值
-
p1 = &b;
-
p2 = &d;
*是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带*。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*。
通过指针变量取得数据
指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:
*pointer;
这里的*称为指针运算符,用来取得某个地址上的数据,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。请看下面的例子:
-
#include <stdio.h>
-
int main(){
-
int a = 15;
-
int *p = &a;
-
printf("%d, %d\n", a, *p); //两种方式都可以输出a的值
-
return 0;
-
}
关于 * 和 & 的谜题
假设有一个 int 类型的变量 a,pa 是指向它的指针,那么*&a和&*pa分别是什么意思呢?
*&a可以理解为*(&a),&a表示取变量 a 的地址(等价于 pa),*(&a)表示取这个地址上的数据(等价于 *pa),绕来绕去,又回到了原点,*&a仍然等价于 a。
&*pa可以理解为&(*pa),*pa表示取得 pa 指向的数据(等价于 a),&(*pa)表示数据的地址(等价于 &a),所以&*pa等价于 pa。
对星号*的总结
在我们目前所学到的语法中,星号*主要有三种用途:
-
表示乘法,例如int a = 3, b = 5, c; c = a * b;,这是最容易理解的。
-
表示定义一个指针变量,以和普通变量区分开,例如int a = 100; int *p = &a;。
-
表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a; *p = 100; b = *p;。
const 和指针
当使用带有const的指针时其实有两种意思。一种指的是你不能修改指针本身的内容,另一种指的是你不能修改指针指向的内容。听起来有点混淆一会放个例子上来就明白了。
-
#include <iostream>
-
#include <stdio.h>
-
using namespace std;
-
-
int main()
-
{
-
int a=1;
-
int b = 100;
-
/*定义指向const的指针(指针指向的内容不能被修改), 初始化与否都可以*/
-
const int *p1 = &a;
-
//const int*p1 = &a; //*的前后有无空白符都无所谓
-
//int const *p1; //也可以先不初始化,另外int和const交换位置无所谓
-
printf("%#X %d\n", p1, *p1); // 输出值为1
-
a = 2; //OK,仍然可以通过原来的声明修改值,
-
printf("%#X %d\n", p1, *p1); // 输出值为2
-
p1 = &b; //OK,指针还可以指向别处,因为指针只是个变量,可以随意指向;
-
printf("%#X %d\n\n", p1, *p1); // 输出值为100
-
//*p1=8; //不正确(指针指向的内容不能被修改)
-
-
/*定义const指针(由于指针本身的值不能改变所以必须得初始化)*/
-
int * const p3=&a;
-
printf("%#X %d\n", p3, *p3); // 输出值为2
-
a = 3;
-
printf("%#X %d\n", p3, *p3); // 输出值为3
-
*p3 = 4; //正确
-
printf("%#X %d\n\n", p3, *p3); // 输出值为4
-
//p3 = &b; //不正确(指针本身的值不能改变)
-
-
/*指针本身和它指向的内容都是不能被改变的所以也得初始化*/
-
const int * const p4=&a;
-
//int const* const p4=&a; //另外一种形式
-
printf("%#X %d\n", p4, *p4); // 输出值为4
-
a = 5; //OK,仍然可以通过原来的声明修改值
-
printf("%#X %d\n\n", p4, *p4); // 输出值为5
-
//p4=&b;//不正确 (指针本身和它指向的内容都是不能被改变)
-
//*p4=7; //不正确(指针本身和它指向的内容都是不能被改变)
-
return 0;
-
}
结果:
0X758BCE14 1
0X758BCE14 2
0X758BCE10 100
0X758BCE14 2
0X758BCE14 3
0X758BCE14 4
0X758BCE14 4
0X758BCE14 5
阅读(1934) | 评论(0) | 转发(0) |