您的位置:首页 > 编程语言 > C语言/C++

C语言基础知识(一)

2017-04-16 20:56 113 查看
编写简单的C程序:HelloWorld
1、编写简单的C程序HelloWorld
#include<stdio.h>
int main()
{
printf("Hello World!\n");
return 0;
}

程序讲解:
#include<stdio.h>:标准I/O头文件,下文中的printf需要使用这个文件
main():主函数,一个程序的入口。一个程序有且只有一个main()函数
int:代表主函数的返回值类型是int型
printf():格式化输出,将括号内的内容打印到显示器上,打印字符串需要用双引号""引上
return 0;:配合主函数类型使用,一般来说返回0的程序表示程序运行成功
2、编译器GCC:把程序代码变成可执行程序
1)GCC是GNU C Compiler的缩写,是开源的C语言编译器
2)把hello.c变成可执行程序步骤:
⒈gcc hello.c -o hello
-o的作用:生成的产物的名字
⒉如果不加-o和名字,则默认生成a.out文件
gcc hello.c
则生成产物为a.out
⒊make hello
此时会调用系统默认的Makefile,编译hello.c生成hello。
等价于gcc hello.c -o hello
执行该程序:
./hello(./a.out)

3)注释:
注释是解释代码用的文本,是编写程序代码的人为了方便理解代码所写的文本。
常见注释:
行注释:使用//,注释一行
块注释:使用/**/,注释一段
被注释的文本不会被执行
#include<stdio.h>
int main()//这是一个行注释
{
printf("Hello World!\n");//这是一个行注释
/*
这是一个块注释
printf("Hello Student\n");//这行被注释了不会执行
*/
//printf("Hello Boys and Girls\n");//这行被注释了不会执行,去掉行首注释即可执行
return 0;
}
/**************使用条件编译快速注释代码***********************/
当需要注释的代码量较多时,如果使用块注释(/**/)则会出现无法配对等情况
此时可以使用以下方法来快速注释一段代码:
#if 0
待注释代码……
#endif
此时就注释了一段代码。
当需要取消这段注释时,只需将0改成1即可
#if 1
待注释代码……
#endif
/**************使用条件编译快速注释代码end********************/
4)C语言从代码变成可执行程序的步骤:
预处理 -----> 编译 -----> 汇编 -----> 链接
⒈预处理:去掉注释,加载头文件,代替宏定义,条件编译
需要文件:.c文件
生成产物:预处理文件(以.i结尾)
使用方法:gcc hello.c -E -o hello.i
可以使用vim打开预处理文件来查看生成产物
⒉编译:使用编译器进行C语言的语法检查,如果有语法错误,报错,并结束编译过程;如果没有语法错误,把C的源程序转变为汇编代码
需要文件:.i文件
生成产物:汇编文件(以.s结尾)
使用方法:gcc hello.i -S -o hello.s
可以使用vim打开汇编文件来查看生成产物
⒊汇编:把汇编源文件通过汇编器生成目标文件(二进制机器语言)
需要文件:.s文件
生成产物:机器码(或称为“目标代码”,以.o结尾)
使用方法:gcc hello.s -c -o hello.o
可以使用vim打开目标代码文件来查看生成产物(不过只会看到乱码)
⒋链接:把目标文件执行所依赖的所有二进制的其他目标文件及C的库文件都整合成一个可执行文件的过程
需要文件:.o文件及各种动态库或静态库
生成产物:可执行程序
使用方法:gcc hello.o -o hello

-o:指定生成的产物的名字
-Wall:让编译器报告全部错误
我们要养成良好习惯,在编译过程中添加-o指定生成产物名称,添加-Wall报告所有的error和warning方便我们调试程序。完整的编译指令如下:
gcc hello.c -o hello -Wall

编译完成后(无error,无warning),会生成-o之后的文件(如没有加-o则会生成a.out文件)
执行文件:
./hello(./a.out)

3、C语言的编程规范:
1)命名规范:
⒈在所有命名中,都应使用标准的英文单词或缩写。
⒉所有命名都应遵循达意原则,即名称应含义清晰、明确。
⒊所有命名都不易过长,应控制在规定的最大长度以内,一般不超过32个字节。
2)编码规范:
⒈在一行代码内只写一条语句;
⒉在嵌套的函数块中使用一个TAB缩进;
⒊每行长度不应超过80个字符;
(如超过,在行尾加\表示换行)
⒋一个函数建议不要超过200行代码;
⒌程序中结束符号;(分号)前面不要加空格;
⒍为程序添加适当的注释;
3)代码要求:
⒈代码格式清楚、层次分明;
⒉代码易于理解、可读性强;
⒊代码具有健壮性,且没有错误;

有关于Linux C的编码规范,我们强烈推荐阅读linux kernel coding文档(读1~8章和12章)
百度linux kernel coding(中文版:百度linux kernel coding cn)

第四部分:标识符,变量与常量
1、标识符
用来标识变量名、符号常量名、函数名、类型名、文件名等的有效字符序列;
1)标识符的命名规则:
⒈标识符只能由字母、数字和下划线三种字符组成,且第一个字符必须为字母或下划线;
⒉C语言中的标识符大小写敏感;
⒊用户自定义的标识符不能与关键字同名;

2)关键字
关键字:对编译器具有特定含义的标识符,是标识符的一个特殊的集合。
C语言有32个关键字,且所有的关键字都是小写
/*******************************C语言关键字*****************************************/
1、基本数据类型
void:声明函数无返回值或无参数,声明无类型指针,显示丢弃运算结果。(C89标准新增)
char:字符型类型数据,属于整型数据的一种。(K&R时期引入)
int:整型数据,表示范围通常为编译器指定的内存字节长。(K&R时期引入)
float:单精度浮点型数据,属于浮点数据的一种。(K&R时期引入)
double:双精度浮点型数据,属于浮点数据的一种。(K&R时期引入)
//_Bool:布尔型(C99标准新增)
//_Complex:复数的基本类型(C99标准新增)
//_Imaginary:虚数,与复数基本类型相似,没有实部的纯虚数(C99标准新增)
//_Generic:提供重载的接口入口(C11标准新增)
2、类型修饰关键字
short:修饰int,短整型数据,可省略被修饰的int。(K&R时期引入)
long:修饰int,长整型数据,可省略被修饰的int。(K&R时期引入)
//long long:修饰int,超长整型数据,可省略被修饰的int。(C99标准新增)
signed:修饰整型数据,有符号数据类型。(C89标准新增)
unsigned:修饰整型数据,无符号数据类型。(K&R时期引入)
//restrict:用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式。(C99标准新增)
3、复杂类型关键字
struct:结构体声明。(K&R时期引入)
union:联合体声明。(K&R时期引入)
enum:枚举声明。(C89标准新增)
typedef:声明类型别名。(K&R时期引入)
sizeof:得到特定类型或特定类型变量的大小。(K&R时期引入)
//inline:内联函数用于取代宏定义,会在任何调用它的地方展开。(C99标准新增)
4、存储级别关键字
auto:指定为自动变量,由编译器自动分配及释放。通常在栈上分配。与static相反。当变量未指定时默认为auto。(K&R时期引入)
static:指定为静态变量,分配在静态变量区,修饰函数时,指定函数作用域为文件内部。(K&R时期引入)
register:指定为寄存器变量,建议编译器将变量存储到寄存器中使用,也可以修饰函数形参,建
182a5
议编译器通过寄存器而不是堆栈传递参数。(K&R时期引入)
extern:指定对应变量为外部变量,即标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。(K&R时期引入)
const:指定变量不可被当前线程改变(但有可能被系统或其他线程改变)。(C89标准新增)
volatile:指定变量的值有可能会被系统或其他线程改变,强制编译器每次从内存中取得该变量的值,阻止编译器把该变量优化成寄存器变量。(C89标准新增)
5、流程控制关键字
1)跳转结构
return:用在函数体中,返回特定值(如果是void类型,则不返回函数值)。(K&R时期引入)
continue:结束当前循环,开始下一轮循环。(K&R时期引入)
break:跳出当前循环或switch结构。(K&R时期引入)
goto:无条件跳转语句。(K&R时期引入)
2)分支结构
if:条件语句,后面不需要放分号。(K&R时期引入)
else:条件语句否定分支(必须与if连用)。(K&R时期引入)
switch:开关语句(多重分支语句)。(K&R时期引入)
case:开关语句中的分支标记,与switch连用。(K&R时期引入)
default:开关语句中的“其他”分支,可选。(K&R时期引入)
for:循环(K&R时期引入)
while:循环(K&R时期引入)
/*******************************C语言关键字end**************************************/
2、变量
1)变量的类型:
常见的变量类型有:
int:整型,保存整数
char:字符型,保存一个字符(字母、数字、其他符号等)
float:浮点型,保存小数
double:双精度浮点型,保存小数范围更大,小数点后位数更多(更精确)
2)变量的定义:
变量必须先定义,再使用
定义一个int型变量:
int a;//定义一个int型变量
定义一个char型变量:
char c;//定义一个char型变量
注意:定义变量不要重名,而且在选择变量名和其它标识符时,应注意做到“见名知意”,即选有含意的英文单词(或其缩写)作标识符。

3)变量的赋值
a = 10;//给int型变量赋值需要整数
c = 'A';//给char型变量赋值需要字符
有时为了书写简便,可以直接在定义变量时赋值。
int a = 10;
char c = 'A';
这种在定义时就直接赋值的使用方法称为“初始化”。
变量之间也可赋值,如:
int a = 10;
int b;
b = a;//将a中存放的值(10)赋值给b
4)打印变量的值
使用printf()来打印变量的值。
要打印a中的值10:printf("%d\n",a);
%d代表十进制输出一个整数。程序运行到这里,printf函数会扫描逗号后面的内容,遇到第一个变量后将这个变量的值放进%d的位置然后输出。
练习:交换杯子里的液体
有两个杯子(用int型代替),其中一个装酒,其中一个装水。如何让两个杯子中的液体交换?
提示:需要第三个杯子
答案:
#include<stdio.h>
int main()
{
int a,b,tmp;//3个杯子,tmp用于中间交换用的空杯子
a = 1;//用1代表酒
b = 2;//用2代表水
tmp = a;
a = b;
b = tmp;
printf("a is %d\n",a);
printf("b is %d\n",b);
return 0;
}

3、常量
1)常量:在程序执行过程中,其值不被改变的量
直接常量:直接引用的数字等;
符号常量:使用标识符来代替一个数字(常见的:宏定义常量 和 常变量)
2)宏定义:又称为宏代换,是定义一个标识符来代表一个值或一个表达式
使用方法:在程序开头使用,#define
#define MAX 10
定义一个宏定义,使用MAX来代替10
练习1:
使用宏定义PI来定义3.1415926,计算圆的面积
答案:
#include<stdio.h>
#define PI 3.1415926
int main()
{
float r,area;
r = 5.0;
area = PI*r*r;
printf("%f\n",area);
return 0;
}
练习2:租船问题。
写程序计算游客需要付的租船的费用。租船每小时30元,押金100元。游客输入租船时间,计算出租船费用。租船费用=时间*每小时钱数+押金。
要求押金与每小时钱数使用宏定义。
答案:
#include<stdio.h>
#define YAJIN 100
#define PERHOUR 30
int main()
{
int hour;
printf("请您输入需要租船的时间:");
scanf("%d",&hour);
printf("需要支付:%d元\n",hour*PERHOUR+YAJIN);
return 0;
}
宏定义的使用阶段:在预处理阶段,将所有的宏换成宏值
宏定义也可定义表达式:
#define ADD(a,b) (a)+(b)
#define MUL(a,b) (a)*(b)
注意:如果要使用宏定义来代替表达式,需要在每个表达式的变量都加上括号以防止出现计算错误
练习:输入程序,对比以下两个宏定义的不同,思考产生不同结果的原因。
#include<stdio.h>
#define FUN1(a,b) a * b
#define FUN2(a,b) (a)*(b)
int main()
{
int a=2;
int b=3;
printf("%d\n",FUN1(a+b,b+a));
printf("%d\n",FUN2(a+b,b+a));
return 0;
}
执行程序,输出:
13
25
答案:
FUN1的宏替换会变成:a+b*b+a
FUN2的宏替换会变成:(a+b)*(b+a)
因此两个宏替换会得到不同的结果。
3)常变量
常变量:变量值不可改变的量,使用const修饰
const int a = 10;//定义一个常变量
注意:
const修饰后的变量会变成只读,因此无法再次赋值。因此初始化常变量需要在定义时直接赋值。
常变量与宏定义常量的区别:
宏定义常量是在预处理阶段,使用宏名来代替宏值。而常变量是在程序运行时,程序在内存分配的一个变量,只不过变量不允许再次赋值。因此宏定义没有占用内存,而常变量占用了内存。
从效率上来说,宏定义的效率高于常变量,因此更加常用
4)常量与后缀:
有时候我们需要显式地表示出常量的类型,这时候我们可以在常量后加后缀
u或U:unsigned类型,如123u
l或L:long类型,如123l
ll或LL:long long类型,如123456ll
f或F:float类型,如0.123f

第五部分:数据类型与类型转换
1、C语言的数据类型分类
C语言的数据类型分类:
基本数据类型:
整型int
字符型char
浮点型:
单精度浮点型float
双精度浮点型double
构造数据类型:
数组(如int a[])
结构体struct
联合体(或叫共用体)union
枚举类型enum
指针类型(如int *p)
空类型void
2、int,unsigned int,short,long
1)整型int:
大小:16位2字节 或 32位4字节(取决于编译器)
存储格式:0~30位是数据位,第31位是符号位,用0代表正数,1代表负数。负数用补码存储。
存储范围:-2^31 ~ 2^31-1
打印格式:%d(十进制)、%o(八进制)、%x或%X(十六进制)
如需要打印八进制和十六进制的特殊格式,加#
%#o(打印八进制数,数前有0表示八进制数),%#x(打印十六进制数,数前有0x表示十六进制数)
说明:
int类型用于存放整数,占16或32位(取决于编译器),2或4字节。其中第31位为符号位(符号位0代表正数,1代表负数),后面几位为数据位
负数在内存中是以补码的形式存储的。
补码的计算方法:按位取反,末位加1
常见的有十进制,八进制,十六进制三种数字进制。
八进制输入输出格式化控制符使用%o或%#o。八进制数以0开头,如0666
十六进制输入输出格式化控制符使用%x或%X或%#x或%#X。十六进制数以0x开头,如0x12345678
/*********************原码、反码与补码**************************************/
计算机只能存储二进制数,因此十进制数、八进制数、十六进制数等在内存中都以二进制的形式存储的。内存中存储的二进制数分原码、反码和补码3种。
原码:将一个十进制数直接转换成二进制数存储
例如:9----->1001(原) 0xFA----->11111010(原)
反码:在原码的基础上,按位取反
例如:9----->0110(反) 0xFA----->00000101(反)
补码:在反码的基础上,末位+1。注意进位问题。
例如:9----->0111(补) 0xFA----->00000110(补)
负数在内存中是以补码的形式存储的。即:负数的二进制存储就是其正数的补码
例如:9----->0111(补)-----> -9 0xFA----->00000110(补)-----> -0xFA
练习:求以下数字的原码、反码、补码。(可以使用计算器)
1、15(10进制)
2、0xE3A4(16进制)
思考:为什么计算机要使用补码来存储负数?
答案:因为数字0。负数是正数的相反数,因此逻辑上来说使用反码存储负数合理。但是,如果使用反码存储负数,数字0会出现问题:
+0--->00000000
-0--->11111111
会发现+0与-0是两个不同的二进制数。但数字0不能够被刻意划分成+0和-0(0只能有一个,即+0与-0必须相等)。因此使用反码存储负数会出现问题。
而补码就不会出现+0与-0不同的问题:
+0--->00000000
-0--->11111111+1--->00000000(最高位的1被舍弃)
因此计算机使用补码来存储负数。

思考:对一个数取2次补码会产生什么结果?
答案:取2次补码等于自身
例如:9----->0111(补)
0111----->1001(补)----->9
/*********************原码、反码与补码end***********************************/

2)无符号整型unsigned int:
大小:同int型
存储格式:0~31位都是数据位,无符号位
存储范围:0 ~ 2^32-1
打印格式:%u
说明:
与int型基本相同,区别是int型的符号位不再是符号位而也作为数据位,因此无符号整型数据比整型数据存储范围大2倍,不过无法存储负数
适用于只有正数的情况
unsigned关键字不仅仅可以修饰int类型,还可修饰short long char等类型。
注意:unsigned关键字修饰的变量无法接收负数。
无符号数的输入输出格式控制是%u
3)短整型short:
大小:16位2字节
存储格式:0~14位为数据位,第15位为符号位,用0代表正数,1代表负数
存储范围:-32768 ~ 32767
打印格式:%hd
说明:
short类型与int类型类似,只不过是16位,2字节。第15位为符号位,后面几位为数据位
short类型适用于存储不太大的数据,节省内存空间。
short类型的输入输出格式控制是%hd
4)长整型long:
大小:32位4字节
存储格式:同32位int类型
存储范围:同32位int类型
打印格式:%ld
说明:
在过去的16位编译器中,int型是16位,所以long类型是32位。
不过在现代的32位编译器中,int型与long类型已无本质区别。
如果需要考虑程序跨平台移植(如16位编译器<--->32位编译器)需要谨慎选择使用int还是long
3、char类型:
大小:8位1字节
存储格式:0~6位都为数据位(128) 或 0~7位都为数据位(256,扩展后的ASCII表)
存储范围:0 ~ 255
打印格式:%c
说明:
char类型也属于整型数据类型(int、short、long)的一份子,只不过char类型运算时使用ASCII表来确定值。与其他三个整型数据类型不同的是,char类型默认就是unsigned类型,而其他三个则默认是signed类型。
/*********************ASCII表与转义字符*******************************/
在计算机的编码中,字符是使用ASCII表进行编码的。每一个字符都有一个对应的数字,例如:
'A'--->65
'a'--->97
'0'--->48 等
除了常见的数字、字母、符号(如+-*/%等)外,ASCII表还存储了一些看不见的控制字符,如:
空或'\0'(字符串结束标志)--->0
空格--->32
任意的ASCII表内字符都可以用'\'+数字(八进制)的方式来表示,有些还可以用'\'+字符来表示,称之为转义字符。转义字符即在'\'后的字符不代表了它本来的含义。
常见的转义字符:
\a:蜂鸣器
\b:backspace退格键
\n:换行,光标移动至下行行首
\r:光标移动至本行行首
\f:换页
\t:tab水平制表符
\\:输出\
\':输出'
\":输出"
\?:输出?
\0:NULL字符串结束标志
/*********************ASCII表与转义字符end****************************/
4、float与double
/*********************浮点数的存储***************************/
1)浮点数存储位划分
浮点数分为float类型(32位)和double类型(64位)
其中最高位是符号位,代表整个数的正负
指数部分实际存储的是移码E,E=e+127(double型中E=e+1023)。若e为正代表小数点向左移动了e位,反之e为负代表小数点向右移动了e位。
尾数部分使用科学计数法,转化成1.XXXXXXX的形式(二进制,浮点数的有效数字),但不存1和.
由此我们可以看出,尾数代表了浮点数的精度,指数代表了浮点数的范围。
2)浮点数存储方法:
示例1:2.5的浮点数存储
⒈先将该数字转化成二进制形式
2.5(十进制)----->10.1(二进制)
⒉移位。移动小数点,使之变成1.XXXXX*2^e的二进制科学计数法的形式
10.1----->1.01*2
⒊舍弃1.,将尾数部分(XXXXX)存数在浮点数的尾数位部分。注意是由高位起存储。其余位补0
01000000000000000000000(尾数)
⒋在第二步的移位过程中,小数点向左移动,因此e的值为+1,获得移码E
E=e+127=128
⒌将E转换成二进制,存储在指数位部分
128(十进制)----->1000000
⒍最后确定这个数是正数,最高位为0。
通过以上几步我们就得到了2.5的浮点数存储
2.5(十进制)----->0 1000000 01000000000000000000000(float类型)
示例2:125.5的浮点数存储
⒈先将该数字转化成二进制形式
125.5(10进制)----->1111101.1(二进制)
⒉移位。移动小数点,使之变成1.XXXXX*2^e的二进制科学计数法的形式
1111101.1----->1.1111011*2^6
⒊舍弃1.,将尾数部分(XXXXX)存数在浮点数的尾数位部分。
11110110000000000000000
⒋在第二步的移位过程中,小数点向左移动,因此e的值为+6,获得移码E
E=e+127=133
⒌将E转换成二进制,存储在指数位部分
133(十进制)----->10000101
⒍最后确定这个数是正数,最高位为0。
通过以上几步我们就得到了125.5的浮点数存储
125.5(十进制)----->0 10000101 11110110000000000000000
示例3:0.5的浮点数存储
⒈先将该数字转化成二进制形式
0.5(十进制)----->0.1(二进制)
⒉移位。移动小数点,使之变成1.XXXXX*2^e的二进制科学计数法的形式
0.1----->1.0*2^-1
⒊舍弃1.,将尾数部分(XXXXX)存数在浮点数的尾数位部分。
00000000000000000000000
⒋在第二步的移位过程中,小数点向右移动,因此e的值为-1,获得移码E
E=e+127=126
⒌将E转换成二进制,存储在指数位部分
126----->01111110
⒍最后确定这个数是正数,最高位为0。
通过以上几步我们就得到了0.5的浮点数存储
0.5(十进制)----->0 01111110 00000000000000000000000

由上文我们可以看出,如果遇到无法完全转化成浮点数的小数(例如0.3这种没法完美转化成二进制数),则浮点数只能存储这个数的近似值而非实际值。因此,浮点数存储的小数不是实际值,只能是近似值。因此,若判断一个浮点数是否等于某数,我们通常不使用==(判等)符号,而是使用判断这个数是否在某个极小区间内的方法。
例如:判断某浮点数f是否等于0
if(f==0)//错误
if(f<0.0000001 && f>-0.0000001)//正确
/*********************浮点数的存储end************************/
1)float类型:
单精度浮点型
大小:32位4字节
存储格式:符号位(1位)+尾数(23位)+指数(8位)
精度:小数部分最多有效7位,实际有效6位(2^23=8388608,所以是7位)
存储范围:-1.17E+38 ~ +3.40E+38(即-2^127 ~ 2^127)
打印格式:%f(十进制计数法),%e(指数计数法)
2)double类型:
双精度浮点型
大小:64位8字节
存储格式:符号位(1位)+尾数52(位)+指数(11位)
精度:小数部分最多有效16位,实际有效15位(2^52=4503599627370496,所以是16位)
存储范围:-2.22E+308 ~ +1.79E+308(即-2^1023 ~ 2^1023)
打印格式:同float类型
注意:double类型是8字节而float类型是4字节,因此double型比float型的数据更加精确,但计算速度会更慢。

思考:若有一个浮点数(float类型或double类型)以%d的方式打印,会出现什么结果?
示例:
#include<stdio.h>
int main()
{
float a = 2.5;
printf("%d\n",a);
return 0;
}
我们会发现打印的是0。
而将a=2.5改成a=2.3
#include<stdio.h>
int main()
{
float a = 2.3;
printf("%d\n",a);
return 0;
}
我们会发现打印的是一个很大的整数。
这是为什么?
答案:由上文我们可以发现,无论float类型还是double类型,打印格式都是%f(或%e),二者的打印格式是一样的。由此我们可以得出结论,无论float类型还是double类型,printf()函数是一视同仁的。
而float类型是4字节,double类型是8字节。因此为了方便,编译器将printf()函数需要处理的数据都转换成double类型(即float类型数据会转换成double类型,double类型不变)。
而在实际输出过程中,printf()函数是根据数据所占内存大小来读取数据的,即打印int类型的时候取4字节。
2.5从float类型转换成double类型后,低32位全部是0。而在打印的时候,printf()函数只取32位(由最低位向前)打印,会取得32个0。因此会打印出整数0。
而2.3因为无法完全转换成二进制数,因此在从float类型转换成double类型后,低32位仍有数据。在打印的时候,printf()取32位后会取得不全是0的一个数,因此会打印出一个很大的数。

3)long double类型:
大小:96位12字节(取决于编译器,有8字节、10字节、12字节、16字节几种情况)
存储格式:未知
精度:不少于double类型
存储范围:不少于double类型
打印格式:%lf,%le
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c语言