【c语言】C语言精华部分

来源:互联网 发布:淘宝永久封店怎么办 编辑:程序博客网 时间:2024/06/05 20:41

经过一段时间的学习,我们的c语言学习已经告一段落,今日特做以总结。

一、关于c语言的一些基础概念

和大多数程序员一样,我们所学的第一个程序就是,在屏幕上输出一句话"hello  world",不过我们的是"hello bit".


再然后我们学习了c语言的一些基础知识。

首先是关键字:

在c99标准中,一共有32个关键字。分别是

auto    break    case    char    const    continue    default   do
double    else    enum    extern    float    for    goto     if     int
long     register     return     short     signed     sizeof
static     struct    switch      typedef     union     unsigned    void
volatile    while

接下来我们学习了c语言中的各种数据类型:

char //字符数据类型  
short //短整型
int //整形
long //长整型
long long //更长的整形
float //单精度浮点数
double //双精度浮点数


并通过sizeof()求出各种数据类型在vs平台上所占字节大小


二、语句

我们学习的语句包含选择语句和循环语句两种:

选择语句包括



循环语句包括:


跳转语句:


break和continue区别:

break是结束本次循环,不再继续执行,continue是结束当前循环,开始下一次的循环。

在学习循环的时候,我们可以设置两种死循环

分别是:

while();和for(;;);

两种循环虽同为死循环,但for(;;);的效率更高一些


将两个死循环分别放在程序中进行运行,点击鼠标右键转到反汇编得到如下图所示结果;

分析可知while的死循环每次都要判断1是否等于零,而for的死循环直接跳进死循环中。

三、数组

1、数组的概念:数组是相同类型数据的集合

2、数组的分类:

分为一维数组和多维数组

3、数组的特性

数组的底层实现是一段连续的空间,支持随机访问

支持下标访问

数组名:在sizeof(arr)和&arr情况下数组名代表整个数组,其他情况下数组名一般代表数组首元素的地址。

#include<stdio.h>int main(){int a[] = { 1, 2, 3, 4 };printf("%d\n", sizeof(a));//16 a代表整个数组,求的是整个数组的大小printf("%d\n", sizeof(a + 0));//4相当于sizeof(a[0])的大小printf("%d\n", sizeof(*a));//4相当于第一个元素的大小printf("%d\n", sizeof(a + 1));//4相当于求第二个元素的大小printf("%d\n", sizeof(a[1]));//4  求第二个元素的大小printf("%d\n", sizeof(&a));//4 a代表整个数组,计算地址的大小printf("%d\n", sizeof(&a + 1));//4 相当于跳过整个数组,数组后面的地址的大小printf("%d\n", sizeof(&a[0]));//4  求首元素地址大小printf("%d\n", sizeof(&a[0] + 1));//4  第二个元素地址大小char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };printf("%d\n", sizeof(arr));//6  数组名代表整个数组求得是整个数组的大小printf("%d\n", sizeof(arr + 0));//4  相当于arr[0] 首元素地址的大小printf("%d\n", sizeof(*arr));//1 求得首元素的大小arr[0]的大小printf("%d\n", sizeof(arr[1]));//1 求得数组第二个元素的大小printf("%d\n", sizeof(&arr));//4 求arr代表着整个数组,求数组地址大小printf("%d\n", sizeof(&arr + 1));//4 求跳过整个数组的后面的地址printf("%d\n", sizeof(&arr[0] + 1));//4 求第二个元素地址的大小return 0;}



通过对以上程序的理解对数组名所代表的涵义更清楚地理解

4、数组的用法

(1)储存元素

(2)传参 二位数组怎么传参

Test(int arr[][])

Test(int arr[10][10])

Test(int *array)

Test(int **array)

Test(int (*array)[10])

(3)返回值

数组初始化:

int array[10] = {0};

int array[] = {0};

int array[]10 = {1,2,3};

5、注意越界访问

#include<stdio.h>int main(){    int  array[10] ;    int i = 0;    for(i=0; i<sizeof(array)/sizeof(array[0]);i++){  array[i] = 0;}    system("pause");    return 0;}

程序局部原理:

多维数组行优先比列优先访问效率高。

怎样动态创建数组?怎么销毁?

#include<stdio.h>  #include<malloc.h>  void f(){int *p = (int *)malloc(2 * sizeof(int));free(p); //释放}int main(){f();return 0;}

四、函数

1、函数概念:
是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于
其他代 码,具备相对的独立性。
一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏

2、定义函数的格式

返回值  函数名(参数列表)

{

//函数体实现某种功能

}

函数传参方式:

1.传参--实参的临时拷贝

2.传地址---实参地址的拷贝

3.(传引用)--实参地址的拷贝

返回方式:

当参数比较小时通过寄存器返回

当比较大时通过参数压栈方式返回

传参的机制:参数压栈

程序运行过程:参数压栈->call FunTest->push返回执行地址位置(保护现场知道应该返回的位置)->跳转jmp

->FunTest入口地址位置

调用约定:函数的调用约定指的是指当一个函数被调用时函数的参数会传给被调用函数和返回值会被返还给调用函数。函数的调用约定就是描述参数是怎样传递和由谁平衡堆栈的,当然还有返回值。

五、常用的库函数


str系列函数 strcpy , strlen, strcat, strcmp, strncpy, strncat,strncmp, strchr等

mem系列函数:memcpy, memmove, memcmp,memset等

数学库函数:cell向下取整 floor向上取整

内存操作:malloc, calloc, realloc,  free

文件操作函数:fopen, fclose, fgetc, fgets, fputc, fputs

I/O方面:scanf   printf 

int scanf(const char* format,...)

int printf(const char* format,...)

atoi--将字符串转换成整数

六、可变参数列表

C语言中的可变参数列表可以使得函数接受任意多个参数(不固定)

编写一个函数实现求平均值操作

#include <stdio.h>#include <stdarg.h>int average(int n, ...){va_list arg;int i = 0;int sum = 0;va_start(arg, n);for(i=0; i<n; i++){sum += va_arg(arg, int);}return sum/n;va_end(arg);}int main(){int a = 1;int b = 2;int c = 3;int avg1 = average(2, a, c);int avg2 = average(3, a, b, c);printf("avg1 = %d\n", avg1);printf("avg2 = %d\n", avg2);
system("pause");return 0;}


1.声明一个va_list类型的变量arg,它用于访问参数列表的未确定部分

2.这个变量是调用va_start来初始化的,它的第一个参数是va_list的变量名,第二个参数是省略号前最后一个有名字的参数,初始化过程

把arg变量设置为指向可变参数部分的第一个参数

3.为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型,在这个例子中所有

的可变参数都是整型。va_arg  返回这个参数的值,并使用va_arg  指向下一个可变参数

4.最后,当访问完毕最后一个可变参数之后,我们需要调用va_end

注意:

1.可变参数必须从头到尾逐个访问。如果你在访问了几个可变参数之后想半途退出这是可以的,但是,如果你想一开始就访问参数列表中间

的参数,那是不行的

2.参数列表中至少有一个命名参数。如果连一个命名参数都没有,就无法使用va_start

3.这些宏是无法直接判断实际存在的参数的数量

4.这些宏无法判断每个参数的类型

5.如果在va_arg中指错误的类型,那么其后果是不可预测的


可变参数的实现是使用宏的封装。只要完成替换我们就可以自行分析了。


七、预处理

程序生成.exe文件的过程

预处理->编译->汇编->链接->.exe

预处理阶段做那些事情?

1.宏替换

2.条件编译指令,比如#ifdef #ifndef #endif

3.包含头文件

4.特殊符号的处理

八、宏



宏的优缺点:

1.提高了程序的可读性,同时也方便进行修改。

2.提高程序的运行效率,使用带参的宏定义既可完成函数调用的功能,又能避免函数的出栈和入栈参数,减少系统开销,提高运行效率

3.宏是由预处理器处理的,通过字符串可以完成很多编译器不能实现的功能,比如##连接符

缺点:

1.由于是直接替换的,所以代码相对较多一点

2.嵌套定义过多可能会影响程序的可读性,而且易出错

3、对带参的宏而言,由于是直接替换,缺少类型检查,不安全

#:把一个宏参数替换成对应的字符串

##:把位于它两边的符号合成一个符号


九、指针

1.指针的概念:指针是一种数据类型,复合数据类型,

指针就是变量,用来存放地址的变量


指针分类:


一级指针和数组:

int array[10];

int *p = array;

sizeof(array);

sizeof(p)->32->4字节

p++ ->指向一段连续空间

p2-p1 ==>两指针间隔元素个数。

指针数组  int* p[10];

数组指针  int (*p)[10];

void (*p)(int, int)函数指针

typedef void (*p)(int , int);

p类型定义函数指针变量

函数指针数组  p fun[10];

野指针:没有合法指向的指针,非常危险

十、自定义类型


1.结构体是什么?

构造数据类型:构造数据类型是根据已经定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。结构体就是构造类型中的一种,在编写代码的时候往往需要多种数据类型,才能实现这个功能,构造体就是把这些数据类型放在一起,重新建立一个新的结构类型。

2.结构体内存对齐

(1)什么是结构体内存对齐:

结构内部包含了大量的未用空间,编译器按照成员列表的顺序一个接一个的给每一个成员分配内存。只有当存储成员需要满足正确的边界对其要求的时候,成员之间才会出现用于填充的额外内存空间。

(2)内存对齐原因:

1、平台原因:硬件平台的限制

2、性能原因:不对齐可能访问两次,对齐访问效率高

(3)内存对齐规则:

1、结构的第一个成员永远放在零偏移处

2、从第二个成员开始,都要对齐到某个对齐数的整数倍处。对齐数结构成员自身大小和默认最小对齐数的最小值(vs环境默认-8 lunux默认-4)

3、结构总大小必须是最大对齐数的整数倍

(4)对齐数计算:

1.结构体成员的对齐:

min(默认对齐数,成员类型);//min()最小值

2.结构体整体对齐:

min(默认对齐数, 结构体中成员类型最大)

(5)如何求取结构体中某个成员相对于结构体起始位置的偏移量:

offset(结构体类型,成员);

(6)大小端

大小端是计算机储存数据的一种方式:

大端:所谓的大端模式,是指数据的高位,保存在内存的低地址中,而数据的低位,保存在内存的高地址中。

小端:所谓的小端模式,是指数据的高位保存在内存的高地址中,而数 据的低位保存在内存的低地址中。

(7)可变长度的结构体

在结构体中加入一个柔性数组

struct Student{  char name[20];  int age;  char gander[3];  char info[0];//柔性数组}

十一、文件操作


fopen(文件路径, 打开模式);//打开文件

fread(文件路径, 打开模式);//读取文件

fwrite(....,..)//写入

fseek(...,...)//移动指针到某个位置

fputc();//输出字符

fputs();//输出字符串

fgettc();//获取字符

fgets();//获取字符串

如何检测是否读取到一个文件的结尾

EOF:实际上是一个宏定义


是文件结尾标志。

还可用feof(pf);函数判断,其中pf是文件指针,EOF只能用来判断文本文件,不能用来判断二进制文件。

十二、动态内存管理

当用数组来储存数据时,数组的大小总在编译阶段就被确定好,想要超过数组储存就会出错,所以引入了动态内存的概念。所以就可以在需要的时候再动态的内存开辟空间。


开辟内存的方式:

int *p = (int *)malloc(sizeof(int)*10);






原创粉丝点击