C语言笔记
基础知识
- 数据类型
序号 |
类型与描述 |
1 |
基本类型: 它们是算术类型,包括:整数类型、浮点类型 |
2 |
枚举类型: 也是算术类型,被用来定义只能使用某些整型值的变量,使用时需要程序员先使用eumn关键字来声明定义 |
3 |
Void类型: 用于函数,指明函数的返回值或参数。作用于变量会发生编译错误 |
4 |
派生类型: 包括:指针类型、数组类型、结构类型、联合体类型、函数类型 |
补充:1.函数类型是指函数返回值的类型,数组类型与结构类型统称为聚会类型。
2.除了基本类型,其他的类型都是程序员使用相关关键词或通过基本数据类型构造的自定义类型。
? |
? | |
类型参考 |
? | |
整数类型 |
Char、int、short、long |
|
浮点类型 |
Float、double、long double |
|
枚举类型 |
enum typeName{ valueName1, valueName2, valueName3, ...... }; |
|
Void类型 |
? | |
指针类型 |
Type * name; |
|
数组类型 |
Type name[count]; |
|
结构类型 |
Struct name{merber1;merber2;merber3}; |
|
联合体类型 |
Union name{merber1;merber;merber}; |
补充:假如要说明整数类型的符号,就在类型前面添加signed/unsigned
参考连接:
????枚举类型:http://c.biancheng.net/cpp/html/99.html
????整型、浮点类型:http://www.runoob.com/cprogramming/c-data-types.html
????联合体类型:http://www.runoob.com/cprogramming/c-unions.html
????结构体类型:http://www.runoob.com/cprogramming/c-structures.html
例子:
#include <stdio.h> #include <string.h> enum week{ Mon = ‘m‘, Tues, Wed, Thurs, Fri, Sat, Sun }; //枚举,值只能是整数类型 struct stu{ char name[8]; char sex; int age; }; //结构体 union school{ ????char name[20]; ????int peopleNumber; ????int phone; ????week day; }; //联合体,成员同时只能存在一个 int main(void){ ????int I = 1; ????int * P; //指针 ????P = &I; ????union school mySchool; ????struct stu student; ????char name[10] = "john"; ????int number[3] = { 1, 2, 3 }; ? ? ????strcpy_s(student.name, "john"); ????student.sex = ‘m‘; ????student.age = 20; ? ????printf("name:%s, sex : %c, age : %d\n", student.name, student.sex, student.age); ????printf("i = %d, p = %d, name = %s\n", I, *P, name); ? ????strcpy_s(mySchool.name, "*****\n"); ????printf("mySchool name : %s\n", mySchool.name); ????mySchool.peopleNumber = 2000; ????printf("mySchool peopleNumber : %d\n", mySchool.peopleNumber); ????mySchool.day = Mon; ????printf_s("day : %c", mySchool.day); ? ????getchar(); ????return 0; } |
?
- 基本数据对象
变量:
????声明:dataType name ;
????特别的是数组变量的声明:dataType name[count] ;
常量:
????声明:#define name value //属于预处理阶段
???????? const dataType name= value; //属于编译阶段
?
- 运算符
运算符 |
? |
算术运算 |
+、-、*、/、%、++、-- |
关系运算 |
== 、 != 、 > 、 < 、>= 、 <= |
逻辑运算 |
&& 、 || 、 ! |
位运算 |
& 、 | 、^ 、<< 、 >> 、~ |
赋值运算 |
= 、+= 、-= 、*= 、/= 、%= 、 <<= 、>>= 、&= 、^= 、|= |
杂项运算 |
sizeof() 、* 、& 、?: |
?
- 控制流
判断:
if( condition ){ Statement block; } else if( condition ){ Statement block; } else{ Statement block; } |
? ? switch( condition ){ case 常量表达式:Statement block; case 常量表达式:Statement block; default : Statement block; } |
?
循环:
1. while( condition ){ Statement block; } |
? do{ Statement block; }while( conditon ); |
2. for( int I ; I >10 ; I ++ ){ Statement block; } |
? |
循环控制: break : 跳出当前循环,不再执行 continue : 跳出当前循环,并继续 |
?
- 程序组织结构
- 预处理语句
C 预处理器不是编译器的组成部分,但是它是编译过程中一个单独的步骤。简言之,C 预处理器只不过是一个文本替换工具而已,它们会指示编译器在实际编译之前完成所需的预处理。
所有的预处理器命令都是以井号(#)开头。
指令 |
描述 |
#include |
包含一个源代码文件 |
#define |
定义宏 |
#undef |
取消已定义的宏 |
#ifdef |
判断宏的定义情况,如果宏已经定义,返回真 |
#ifndef |
判断宏的定义情况,如果宏没有定义,返回真 |
#if |
如果给定的条件为真,则编译下面的代码 |
#else |
? |
#elif |
? |
#endif |
? |
#error |
当遇到标准错误时,输出错误消息 |
#pragma |
使用标准化方法,向编译器发布特殊的命令到编译器中 |
补充:#pragma comment(linker, "/entry:main2") 可以用来改变入口函数
- 函数
先声明后使用
定义: return_type function_name( parameter list ){ Statement block; } 使用: function_name(parameter list); |
????补充:对应一个可执行文件的源代码,始终要存在一个主函数,才能让编译器正确编译。
???????? 默认main 函数,但不一定是main,可以通过设置编译器的参数来进行更改。
- 变量
先声明后使用
- 语句&表达式
语句是对基本数据对象的处理步骤,使用 ; 分割;表达式是对基本数据对象进行运算,处理步骤成为了语句。
- 注释
// 或者 /* 内容 */
进阶部分
标准库:
应该知道的:标准库并不是c语言本身的构成部分,只是将一些非常非常常用的功能进行了标准化,让程序员能够更好的进行程序的开发和移植。并且提供一些系统调用的封装,这样程序员就可以不用去考虑程序与系统与硬件的关系,只要调用相关的库函数就能进行与硬件的交互了。
标准库中包含了大量的宏定义和使用typedef 关键字定义的新的类型,以及函数的声明与实现。
?
标准库名称 |
说明 |
常用库函数 |
<stdio.h> |
标准输入输出:用以提供基于常用硬件的I/O操作(文件读写、屏幕输入输出)。 |
fclose、fopen、fseek、fwrite、printf、 scanf |
<stdlib.h> |
定义了四个变量类型、一些宏和各种通用工具函数 |
NULL、atof(字符串转换为一个浮点数)、atoi、free、malloc、abort、 exit、getenv、 |
<string.h> |
与操作字符串有关 |
memcmp(将str1与str2的前n个字节进行比较)、strcat、strcpy、strlen、 |
<stdarg.h> |
与函数的参数有关,获取参数个数可变时获取函数中的参数 |
? |
<math.h> |
定义各种数学函数 |
exp(x):返回e的x次幂的值 pow(x,y):返回x的y次幂 fabs(x) :返回x的绝对值 |
<time.h> |
? | ? |
<stddef.h> |
定义各种变量类型与宏 |
NULL、 |
<assert.h> |
提供一种诊断宏型函数 |
唯一的库函数:assert |
<ctype.h> |
提供函数来测试和映射字符 |
islower、isupper、isxdigit、tolower、toupper … |
<errno.h> |
定义了一些与错误有关的宏 |
errno |
<float.h> |
包含一组与浮点值有关的平台常量 |
FLT_MAX:平台能表达的的最大浮点数,FLT_MIN:平台能表达的的最小浮点数 |
<limits.h> |
定义在其中的宏限制了各种变量类型的值 |
CHAR_BIT:定义一个字节的比特数 |
<locale.h> |
定义特定地域的设置,比如日期格式和货币符号 |
LC_CTYPE:影响所以的字符函数 setlocale : 设置或读取地狱化信息 |
<setjmp.h> |
程序中的长跳转,不局限在函数内部。 |
setjmp/longjmp的最大用处是错误恢复,类似try ...catch... |
<signal.h> |
提供与信号有关的宏和函数 |
signal : 设置一个函数来处理信号 raise() : 产生信号 |
<raise.h> |
用于产生信号 |
? |
?
共享库与静态库、静态调用与动态调用、静态链接与动态链接
静态库与动态库的编译方式不同。属于编译部分。
静态链接与动态链接是属于链接部分的,是告诉链接器使用的是什么类型的库,静态库就是静态链接,动态库就是动态链接。
#pragma comment( lib, "..\\debug\\libTest.lib" ) //告诉连接器使用静态库进行静态链接
静态调用和动态调用是属于动态库的一部分,是关于如何将动态库加载到内存的。
静态调用时,操作系统的加载程序会先为进程创建虚拟地址空间,接着把可执行模块映射到进程的地址空间中,之后加载程序会检查可执行模块的导入段,试图对所需的DLL进行定位并将它们映射到进程的地址空间内。(windows 核心编程第五版 19章中的运行可执行模块)。#pragma comment(lib,"dllTest.lib") 这个宏告诉编译器使用静态调用。
动态调用时,由程序员通过loadLibrary这个API将DLL映射到进程的地址空间中,再由程序员通过GetProcAddress这个API获取想要的导出函数的地址(在使用返回的函数指针来调用函数之前,需要转型为与函数的签名相匹配的正确类型)来调用所需函数。
外部函数:
由其他源文件或者库提供的函数。程序员使用时,需要它人提供的头文件或者使用extern或者extern "c"__declspec(dllimport)来进行函数声明,表明该函数来至外部文件。
编译:
链接:
软件的运行过程:
操作系统与软件的关系:
系统调用:
低层c与应用层的c的关系:
?
系统调用与标准库的关系:
系统调用就是操作系统提供的一组API,通过这些API函数就可以通过系统与硬件进行交流了,这些API函数需要到操作系统的开发者处获得。而语言的标准库是语言的标准化组织为了让程序员在各种平台或者系统上进行少量的程序修改就可以编译运行所制定的一系列的函数。也就是说标准库是脱离了操作系统或者平台的,属于是抽象层,具有平台无关性。
但是每个操作系统能够提供的API(系统调用)并不是一致的,也就是函数名呀、函数的参数呀、返回值类型呀会有所不同。
但为了让程序能够运行在某种特定的系统上,就需要去实现该系统上的标准库中的库函数(标准函数)。而这部分会由编译器来提供,就是说编译器的开发者会提供该编译器能编译的语言的标准库部分(标准库的实现)。他们通过阅读操作系统开发者提供的API文档来编写标准库的实现代码。
到此处就可以了解到程序员只需调用对应的库函数就能摆脱操作系统,来编写自己的可移植的程序代码了(标准库消除平台的差异性,给程序员提供一种统一体验的编码方式,让程序更方便的移植)。
库代码解析:
????补充:在vs2013中开发没有使用纯c的,只有使用c++的源代码了。但是c++是c的升级,向下兼容c,所有差别不大,只是在语法上有些出入。
windows中的vc: time.h #include <time.inl> //采用了c++的内联语法 ? time.inl //内联 static __inline time_t __CRTDECL time(time_t * _Time) //32位 { return _time32(_Time); } ? time.c //库函数的实现部分 #include <#include <time.h> #include <windows.h>> //操作系统提供的系统调用的头文件 __time32_t __cdecl _time32 ( __time32_t *timeptr ) { __time64_t tim; FT nt_time; ? GetSystemTimeAsFileTime( &(nt_time.ft_struct) ); //系统调用 ? tim = (__time64_t)((nt_time.ft_scalar - EPOCH_BIAS) / 10000000i64); ? if (tim > (__time64_t)(_MAX__TIME32_T)) tim = (__time64_t)(-1); ? if (timeptr) *timeptr = (__time32_t)(tim); /* store time if requested */ ? return (__time32_t)(tim); } |
可以看出库函数 time 的实现其实是调用了windows.h中的GetSystemTimeAsFileTime系统调用。
补充:由于库的实现不是应用程序员所需要关心的事情,程序员只关心提供的 .h 文件中的函数声明(也就是标准库提供的接口的方式),所以编译器开发者可以使用高超的技术来进行各种优化与数据操作(也就是我们看不懂的方式来进行库的实现)。所以不必在意库的实现。
glibc ? time.h # include <bits/time.h> extern time_t time (time_t *__timer) __THROW; //使用liunx提供的time ? time.c time_t time (timer) time_t *timer; { __set_errno (ENOSYS); ? if (timer != NULL) *timer = (time_t) -1; return (time_t) -1; } ? <sys/time.h> extern int gettimeofday (struct timeval *__restrict __tv, ???????????? __timezone_ptr_t __tz) __THROW __nonnull ((1)); |
?
由于能力有限不能找到具体的函数实现,但是可以通过网络了解到liunx上的c标准库的实现使用了linux提供的time系统调用,采用的是函数映射方式实现的。
liunx上获取时间的系统调用为 #include <sys/time.h>中的gettimeofday。
再次补充:一般地,操作系统为了考虑实现的难度和管理的方便,它只提供一少部分的系统调用,这些系统调用一般都是由C和汇编混合编写实现的,其接口用C来定义,而具体的实现则是汇编,这样的好处就是执行效率高,而且,极大的方便了上层调用。
写在最后:一种编程语言只是抽象层的东西,要让计算机能够识别该种语言所编写的程序,就需要编译器或者解释器,将程序员编写的属于字符类型的源代码文件转换成计算机(cpu)所能懂的指令。由于计算机分成了很多的平台与架构(操作系统的不同,cpu的差异),所以就要编写各种的编译器或解释器来进行该平台的语言实现,实现语言中的语法和语义,并且提供语言标准组织所制定的标准部分(要提供什么标准库和其他编程方面的东东)(但不一定非要这样,也可以增删部分语法和库)。所以编译器或解释器是最终得boss,它拥有语言实现的决定权。说白了程序员就是一个给编译器或解释器打工的苦劳力,最终你的工作成果还的它来说了算。管他吗的语言标准呀,说他妈的可移植的语言,没有实现,都是空话。还有就是罗马不是一天建成的,现在的一切程序都是由最开始的cpu指令构成的(人不就是史前的那个单细胞慢慢演变的吗?)。