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

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

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

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

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

 

注释:

c语言注释/**/

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平台上所占字节大小

 

常量:

常量的分类:
1、字面常量:

2、const定义的常量(具有常属性的变量)

3、#define定定义的标识符常量

4、枚举常量

 

变量:

变量的命名规则:必须以下划线或者字母来头,后面可以跟若干个字母、数字、下划线,但不能有其他字符。也不能使用关键字

 

变量的分类:

1、局部变量

2、全局变量

 

static 修饰局部变量:变量放在静态区,生命周期,整个程序运行期间

变量的初始化赋值:创建变量的同时给一个初始值

表达式求值和表达式的属性:

我们必须关注表达式的两个属性:值属性和类型属性

值属性:决定了计算结果是多少,

类型属性:决定了表达式的值能否复制给其他变量

 

c语言的特点:

(1)c是一种通用的编程语言,广泛应用于系统软件和应用软件的开发。

(2)c语言具有高效、灵活、功能丰富、表达力强和较高的可移植性等特点

 

c语言de特色:

(1)c语言是一个有结构化程序设计、具有变量作用域以及递归功能的过程式语言

(2)c语言传递参数均是以值传递,另外也可以传指针

(3)不同的变数类型可以用结构体组合在一起

(4)只要32个保留字,使变量、函数名有更多弹性

(5)部分的变量类型可以转换,例如整型和字符型变量。

(6)部分的变量类型可以转换,例如整型和字符型变量。

(7)通过指针,c语言可以容易的对存储器进行低级控制

(8)编译预处理让c语言的编译更加有弹性

二、语句

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

选择语句包括

 

 

if语句的书写规范:
(1)bool值和零比较

bool类型是c99标准才引入的一个语法特点,用的很少,一般情况下我们都用整型变量来充当一个bool变量的角色,因为c语言中bool值的语义是:0表示假,非零表示真

(2)整型变量和零比较

如果一个变量就是整型(不表示真假),那么这个变量如何跟0比较

int num = 10;
if(num == 0)
{
    printf("num==0\n");
}

不要写成:

int num = 10;
if(num)
{
    printf("num==0\n");
}

(3)指针和零进行比较

int *p;
*p = 20;

指针变量要初始化才能使用。,所以上面的代码错误。

int *p = NULL;
if(p!=NULL)
{
    *p =  20;
}

(4)浮点数和零比较

因为浮点数在内存中的存储方式使得,有些浮点数在内存中无法精确的存储,这样就必然有精度的丢失。一旦丢失就有可能算不准确。无法使用==直接判断两个浮点数相当。

这就需要设置一个能接受的精度,保证误差在京都范围内就好了。

#define EXP 0.000000001
float f = 0.000001;
if((f>-EXP)&&(f<EXP))
{
    //就意味着f是无线接近于0的数字,约等于0
}

 

goto语句:

c语言中提供了可以随意滥用的goto语句和标记跳转的标号。

循环语句包括:

跳转语句:

break和continue区别:

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

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

分别是:

while();和for(;;);

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

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

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

建议1:在多重循环中,如果有可能,应当将最长的循环放在最内层,最短的循环放在最外层,以减少CPU跨切循环层的次数

建议2:如果循环体内存在逻辑判断,并且循环次数很大,宜将逻辑判断放在循环体外面

折半查找算法:

每次取中间数。

 

三、数组

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

2、数组的分类:

分为一维数组和多维数组

3、数组的特性

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

支持下标访问,下标从0开始

数组名:在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.(传引用)--实参地址的拷贝

 

函数之间的组合方式:

(1)嵌套调用

(2)链式访问

返回方式:

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

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

传参的机制:参数压栈

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

->FunTest入口地址位置

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

函数声明:

(1)告诉编译器有一个函数叫什么,参数是什么,返回类型是什么,但是是否存在无关紧要

(2)函数的声明一般出现在函数的使用之前,保证先声明后使用

(3)函数的声明一般是放在头文件里的

函数的定义:

函数的定义指函数的具体实现

链接属性:

(1)外部链接属性:意味着一个标识符不仅可以在当前源文件使用,也可以使用extern声明可以在其它源文件使用

(2)内部链接属性:具有外部链接属性的加上static关键字,就变成内部链接属性

(3)无属性:局部变量是无属性的

五、常用的库函数


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.特殊符号的处理

5.删除注释

6.添加行号和文件名标识

7.保留所有的#pragma编译器指令,因为编译器需要使用它们

编译阶段要做的事:

1.词法分析

2.语法分析

3.语义分析

4.符号汇总

5.优化后生产相应的汇编代码文件

汇编阶段要做的事:

1.形成符号表

2.将汇编指令翻译成机器所能识别的二进制指令

链接阶段所要做的事:

1.合并段表

2.符号表的合并和符号表的重定位

运行环境:

程序执行的过程:

1.程序必须载入内存中

2.程序开始执行

3.开始执行程序代码

4.终止程序

八、宏

 

宏的优缺点:

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

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

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

4.宏是类型无关的

缺点:

1.由于是直接替换的,所以代码相对较多一点,当宏比较长时,会大幅度增加程序的长度

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

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

4、没有办法调试

5、可能会带来算术优先级问题

6、带副作用的宏参数

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

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

 

九、指针

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);

 

 

 

 

 

 

 

 

 

 

  • 4
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值