关于C语言的几点总结

来源:互联网 发布:没有钱想去美国知乎 编辑:程序博客网 时间:2024/04/30 10:24

第一部分:C语言的基本元素——数据类型、变量、运算符、流程控制、函数

第一点:数据类型

       作为一门语言,最基本的是数据类型,在C语言中基本类型包括int、char、float、double、enum类型;C语言的构造类型包括数组、结构体(struct)、共用体(union);C语言还有一个最为重要的类型----指针;除了以上类型,C语言还定义了void类型。需要注意的是,C语言没有bool型数据,虽有真假逻辑值的概念,但没有bool这种类型,不能像C++里那样定义bool变量。

       C语言是一门面向过程编程的语言,解决问题的思路是将问题的解决办法分步,然后用C语言的语句、函数来一步一步实现。这其中就涉及到两个必须把握的点:一是数据类型的选择、一是算法。这里插一句,和C++的区别在于C++引入了类的概念,这一引入大大扩展了C语言,也使得编程从面相过程转变为面向对象编程。面向对象编程的思路是,针对一个具体问题,定义具体的数据类型和操作,再用该类去实现所需操作。

回到C语言的数据类型,要想知道什么时候使用什么类型的对象,需要掌握每种类型的特点。在此提以下几点,曾经我一直记不住的几点:
  1. 整型常量有三种表示方法:十进制、八进制(以0开头)、十六进制(以0x开头)
  2. 在内存中,数据实际上是以补码形式表示的:正整数的补码和原码相同;负数的补码是将该数的绝对值的二进制形式按位取反再加1(或者:对该负数的二进制码,符号位不变,其余位取反加1)。
  3. 可以在整型常量后面添加后缀U或L(U和L大小写皆可)来指定其类型为unsigned或long。
  4. 浮点型数据有两种表示方法:十进制小数形式(编译器默认将其视为double类型,若要指定为float可以添加后缀f,例如3.14f)、指数形式(3.141593e0)
  5. 区分开浮点数据的有效位数和输出位数,并不是所有输出的浮点数据都是有效的:float有效位为7位,但一般在保证整数部分全部输出的同时还会输出6位小数;double的有效位为16位,同样在保证整数部分全部输出的同时也会输出6位小数。(我曾经就在做前缀加的时候遇到这种情况,两种算法都正确但最终得出来的结果差别很大,这一差别就是因为当时数据太大,超过了有效位数导致的,一开始还莫名其妙的找原因,想起来真是太傻了。)
  6. 对于char类型,最好记住几个特殊字符的ASCII码:空字符null---->‘\0’---->00H;数字0---->'0'---->30H;大写字母A---->'A'---->41H;小写字母a---->'a'---->61H。
  7. char数据在内存中是以ASCII码存储的,所以可以和整数直接进行算数运算,也可以以%d输出ASCII码。
  8. 各类型数据之间作混合运算时的转换方向也值得注意,值得一提的是有符号和无符号数做算术运算时,有符号的数据当做无符号处理。

第二点:运算符

C语言的运算符范围很广,他把除了控制语句和输入输出以外的几乎所有基本操作都作为运算符处理。例如:算术、自增自减、关系、逻辑、位、赋值、条件、逗号、指针、求字节(sizeof)、强制类型转换、分量(“.”和“->”)、下标、函数调用等都是运算符。这些运算符是构成语句的另一元素,在这里我最关心的还是这些运算符的优先级,这个在网上可以找到相关表格,以下按高到低的顺序排列了一些:
逻辑非、算术运算、关系运算、条件运算、&&和||、赋值运算

第三点:程序控制语句

C语言中有三种控制结构:顺序结构、选择结构、循环结构;基本控制语句:if、for、while、do……while、continue、break、switch、goto、return
一个C程序,不管你包含多少个源文件、多少个头文件、多少个函数,其基本元素都是语句。
另外注意一点,不要将putchar、getchar、printf、scanf、puts、gets、strcat、strcpy、strncpy、strcmp、strlen、strlwr、strupr等看成操作符,他们是C标准库函数,声明在stdio.h等头文件中。

PS:字符串与字符数组以及字符指针之间的关系归纳:
/***************************************************************************************************************************************************************************/
1. char *p 与char p[]的区别?

根本区别:
char *p定义的是一个指针,但该指针还不指向具体内存,不能对其读写;
char p[]定义的是一个数组,是分配有内存空间的,可以对其进行读写;

赋值方式:
char *p:
            字符串常量初始化(此时不允许修改p的内容,也不能使用strcpy往里面copy内容)
            相同类型的指针(即地址);
            分配(malloc/calloc)空间后memcpy/strcpy/strcncpy。
char p[]:
            字符串常量初始化(初始化之后允许修改);
            逐个元素赋值;
            memcpy/strcpy/strncpy。

总结:
记住一点,未分配空间的地址,一律不能往里面写数据(字符串常量初始化一个指针,是将该指针指向了一个不可写的内存快,因此也不能往里写内容)。

2. strcpy、sprintf、memcpy的区别

1)char *strcpy(char *des, char *src); 
            其操作对象是字符串,完成从源字符串到目的字符串的拷贝。
2)int sprintf(char *buffer, const char *format, ...);
            其操作对象不限于字符串,源对象可以是其他基本数据类型变量。
            这个函数可以用来实现非字符串的基本数据类型向字符串类型的转换。
3)void *memcpy(void *des, void *src, unsigned int n);
            其操作对象是内存块,通常限于同种类型数据或者对象之间的拷贝。
            其大多被用来实现对结构体或者数组的拷贝。
            des 和 src 所指的内存区域不能重叠,否则,应该使用下面函数
4)void *memmove(void *des, void *src, unsigned int n);
            操作对象也是内存块,与memcpy的区别在于,该函数适用于des 和 src 所指的内存区域重叠的情况。
 
strcpy 和 memcpy 在功能上的差别:
      strcpy 当被拷贝字符串中含有 \0 时,则拷贝结束,des 所指向的字符串为 src 所指向字符串中\0前面的子串。
      memcpy 并不是遇到 '\0' 就结束,而是会拷贝完n个字符,不管字符串中是否包含\0 。
/***************************************************************************************************************************************************************************/

第四点:函数

模块化编程是解决问题的一大利器,最好不要将一个问题的解决思路全部写在一个函数里,尽可能将其模块化,每一个模块实现一个小功能,这样也有利于后续开发减少重复性工作。
但是函数的使用带来两个问题:一个是函数的调用问题;另一个是函数以及变量的作用域,我们通常借用一些关键字来扩展或限定变量以及函数的作用域。

函数调用问题:通常一个项目由多个头文件和多个源文件组成,一个源文件又由多个函数组成,可以说函数是整个项目的基本模块。各个模块(函数)之间是通过函数调用组织在一起的,而函数调用有个原则:就是函数只能调用已经存在的函数,这就引申出了函数声明这一概念。下面几点需要额外关注一下。
1.递归调用。
2.函数调用过程中的值传递过程的一些特点:单向传递的值传送方式。这里补充一下,函数参数的类型选择指针还是普通变量是很灵活的,学问很大。
3.带参数的宏定义也可以实现简单的函数调用,他与普通的函数调用之间是有区别的。下面简单提几点:
1)宏定义只做简单的置换,不做正确性检查,所以在使用时务必小心格式,括号的使用是很有必要的。
2)宏定义的作用域从定义开始到该源文件结束。
3)调用函数只可得到一个返回值(除了以数组名或指针作为参数),而用宏可以设法得到多个结果。
4)使用宏会使函数变长,而函数调用不会。
5)宏替换不占运行时间,只占编译时间;函数调用要占运行时间(分配单元、保留现场、值传递、返回)。
所以说,要善于利用宏定义的话能够实现程序的简化。

备注:后续在这里会添加数组名作为函数参数的相关讨论。

作用域问题:
1.首先区分开全局变量和局部变量及他们的作用域。
2.内存中用户使用的存储空间分为3类:程序区、静态存储区、动态存储区。
3.在函数执行过程中存在的变量的存储类别分为两类:静态存储、动态存储
4.作用于变量的一些控制存储方式的关键字:
auto:默认是可省略的,自动变量,被存储在动态存储区内,作用域结束即被释放。
static:用于将局部变量声明为静态,静态局部变量存储在静态存储区,直到函数执行完毕才释放;将外部变量声明为静态,从而禁止其他源文件调用该变量;
register:优点是存储速度快,但仅限于将局部自动变量定义为寄存器变量,全局的或静态的变量已经存放在静态存储区,都不可以再声明为寄存器变量。
extern:用于声明外部变量(在函数之外声明的全局变量),注意这里说的是声明外部变量而不是定义外部变量,他的作用在于将变量的作用域扩展到自声明起到程序结束。
5.内部函数(static)和外部函数(extern,默认可以省略)。
static函数:static将一个源文件中的函数声明为静态的不可被其他源文件调用。
extern函数:extern将一个函数声明为外部函数,在其他源文件中进行extern声明后就可以直接调用。

第五点:预处理命令

首先需要理解的是预处理不是C语言本身的组成部分,是不能直接对他进行编译的。编译器的编译过程实际上分为预处理、编译、汇编、连接四个步骤,其中预处理就是对程序中的预处理命令进行处理的过程。
预处理命令有三类:宏定义、文件包含、条件编译
宏定义:#define……#undef
文件包含:#include
条件编译:
1. #ifdef……#else.......#endif
2. #ifndef……#else.......#endif
3. #if 表达式 ......#else......#endif
这里顺便说一下,条件编译的优点:减少被编译的语句,从而减少目标程序的长度,也节省了运行时间。

第六点:指针

对于指针,我只能说这是一个相当复杂的家伙,它的应用也十分灵活。我只希望我自己能不要忘记指针最根本的东西——指针的本质就是地址,指针变量就是存放地址的变量。另外在对指针作算术加减运算时,需要知道指针的基类型。


后续内容尚待整理。。。


1.指向函数的指针

typedef和#define的区别:
1.#define是在预编译时处理的,typedef是在编译时处理的。
2.#define只做简单的字符串替换,而typedef并不是做简单的字符串替换。
3.typedef对于程序移植常常起到很好的效果。





原创粉丝点击