最近看到自己之前刚开始学习的时候记的一些笔记就稍微整理了一下
第一章
c语言注释
概念:给代码进行解释说明
作用:加强程序代码的可读性和维护性
/*:多行注释
//:单行注释
变量和基本数据类型
变量:一块存储空间,里面写了相应类型的值,通过变量来获取里面的值
变量的本质:内存的存储单元
变量的过程:
1)声明变量->开辟空间
2)初始化变量:向空间里写内容
3)使用变量:从空间里面读取内容
数据类型:int char float double
sizeof(数据类型名)显示每种类型的字节数
变量命名:
以字母和下划线做为开头,由字母和下划线和数字组成,大小写敏感,不能用c语言关键字比如:int、float、const、printf等。
变量初始化3种方式:
1)声明的同时进行初始化
2)运行时用赋值表达式初始化
3)用户输入数值进行初始化
变量前添加const是只读变量的意思:一般我们会直接称作“常量”,但是其实更准确的称为只读变量。位置:内存堆栈。只读的性质由编译器赋予,人为修改编译不过。 只读变量需要初始化,不然编译会报错,因为定义性声明时如果不初始化,之后不允许初始化,这个变量就没有意义了。
输入,输出printf scarf
标准的输入与输出:
包含头文件
输出:printf(".....\n",list...);
内存——>显示器
输入:scanf("......",address list...);
键盘——>内存
类型定义符
typedef关键字:给已有类型起别名
第二章
表达式=操作数+运算符
操作数:变量&常量
算数运算符
二元运算符+、-、*、/
除法运算:操作数分母不能为0
两整数相除,其值取整,小数忽略
取余:操作数分母不能为0;
操作数必须为整数,
自增自减前置++i --i 后置i++ i--
一元
sizeof:类型所占字节
关系运算符&逻辑运算符
逻辑:只有两个值trun false
关系:<= > < >= == !=
一元否定:(!valid)
三目运算符:(表达式1?表达式2:表达式3)
类型转换:
显示转型:(被转换的数据类型)变量
隐式转型:低->高
赋值表达式:右-->左
逻辑数据<->int
char <->int
第三章 控制流:
顺序结构:表达式+分号 int a =10;
程序块 {}
选择结构:if:单路选择
if else:双路选择
if... else if ...:多路选择
switch(条件表达式):case 表达式:语句;
循环结构:
前置测试循环:先判断,再执行
for表达式:
for(表达式1;表达式2;表达式3){......}
while表达式:
while(表达式2)
{
循环体语句;
表达式3;
}
后置测试循环:先执行,再判断
do-while 表达式:
do
{
循环体语句;
表达式3;
}while(表达式2);
表达式1:初始化循环控制变量;
表达式2:作为循环判断条件;
表达式3:循环变量的改变;
循环嵌套:在一个循环语句又包含另一个循环语句。
continue用于循环体内部,作用是结束本次循环,开始执行新的循环。
break跳出循环,不在执行新的循环,只跳出break所在这一层循环。
return :直接跳出循环,并且不再执行新的循环。
第四章
一维数组概念:具有相同数据类型的一组变量的集合;
特点:数组中可以存储相同类型的多个变量;
注意问题:
1)数组元素的内存浪费
2)越界访问;
二维数组格式:类型说明符 数组名[常量表达式1][常量表达式2]. int a[2][3];
引用形式:数组名 [下标][下标]。
第五章
理解为什么要使用函数?(好处)
1)可以更好的组合代码;
2)可以多人合作开发;
3)有利于早发现问题
函数逻辑?输入+执行+输出
函数定义: 函数头+函数体
返回值类型 函数名(函数的形参列表)
{
函数执行体语句
}
函数名的使用;
a、声明函数;编译阶段
b、函数的实现:执行阶段,实现对函数的具体功能
c、使用函数:函数的调用(传参)
C语言中函数的传参:传值、传地址(传指针)
传值的原理:主调函数、被调函数、形式参数、实际参数
1) 传值方式,在函数调用时,主调函数把实参变量的值拷贝一份,赋值给被调函数的形参变量
2) 在被调函数体内,操作和修改的是形参的值,实参不受影响
3) 只是单向传递,从实参到形参
函数的嵌套(调用的嵌套)
递归函数(特殊的嵌套函数)
原理:取代了循环
注意:(死循环)
变量与函数之间的关系:
左值:可做右值
右值:不可做左值
分类:全局变量与局部变量(作用域范围、生命周期)
static修饰的变量:全局变量与局部变量(作用域范围、生命周期)
函数:全局函数与static函数(作用域范围、生命周期)
外部文件中全局变量与全局函数的使用:extern关键字修饰
静态局部变量
(1)未经初始化时系统默认初始化为0;
(2)生命周期:编译——程序结束
(3)可以保留上一次执行后变量的值
全局变量和局部变量在程序中的生存期和作用域对比?
生存期 作用域
普通全局变量 整个程序 整个程序
static全局 整个程序 只能用于本文件内部
普通局部变量 函数域 函数域或程序块
static局部 整个程序 函数域或程序块
第六章指针
a、指针类型与地址的理解?
b、指针数据类型的理解
c、指针变量的理解?特点
d、指针变量的赋值与访问操作?
指针变量的声明方式(三种),每种写法的意义
指针初始化问题?
面试考题:野指针与空指针的区别
指针作为函数的参数:原理?解决的问题?
指针作为函数的返回值类型?
面试考题:为什么不能返回一个局部变量的地址(局部变量的指针)
内存分配问题的理解
面试考题:
1、变量在内存中的分配存储区域(5大区)各自的特点
2、变量的存储类型分为两种情况:
第一种:按照空间的角度(作用域范围)分为:全局变量存储和局部变量存储?
第二种:按照时间的角度(生命周期)分为:静态存储方式和动态存储方式?
堆区与栈区的存储区别?
3、堆区内存管理函数的使用:
malloc、calloc、realloc、free的作用以及应用?
二维指针的理解:
多维指针;
指针和数组:重点:数组的名字就是一个指向数组首元素的指针
指针数组:数组里每个元素内部存放的是地址编号
数组指针:一个指针,指向数组整体;
指针的运算可以-,不可以+
参数传递:
传指针,通过对其形参的间接改变,改变实参的值
指针与const的关系
指针加const指针不能改变指向
int * const p
const int * p
函数指针:
指向函数的指针包含了函数的地址,可以通过它来调用函数
格式:类型说明符 (*函数名)(参数)
确定一个函数指针的类型就是指针指向的函数的参数类型和返回值类型
第七章 字符串
字符串:用双引号包围;结束标志‘\0’
字符数组:数组每个元素都是字符,字符用单引号包围。
字符数组初始化方式 两种
可变字符串:
可以把一个字符串赋值给一个char*类型的指针,但是不能通过指针来修改这个字符串。
常用的字符串操作函数
strlen
原型:unsigned long strlen(const char* s)
返回字符串的字符数长度,有效长度
strcpy
原型:char* strcpy(char * to,const char* from)
拷贝一个字符串;
to必须指向有效的足够大的可操作的存储空间
from的内容在内存上粘的存储空间必须小于等于to所指定的存储空间
strcat
字符串链接函数;
strcmp
原型:int strcmp(const char *str1,const char* str2)
字符串比较函数;
4、字符串数组
1、字符串数组的理解:
字符串数组存储的,不是每一个字符串的内容,而是各个字符串首地址的指针。其定义形式如下所示有三种:
(1)特点:该字符串数组中的内容可以被修改
char str1[]="hello";
char str2[]="world";
char str3[]="!";
char * array[]={str1,str2,str3};
int i;//遍历访问
for(i=0;i<3;i++)
{
printf("%s\n",array[i]);
}
(2)特点:该字符串数组中的内容不可以被修改(通过指针来访问常量字符串)
char * str1="hello";
char * str2="world";
char * str3="!";
char * array[]={str1,str2,str3};
(3)
特点:该字符串数组中的内容不可以被修改(通过指针来访问常量字符串)
char * array[]={"hello","world","!"};
2、字符串数组名可以被看作是一个指向字符型指针的二维指针。
字符串数组的理解(二级指针+二维数组)
5、文件操作
1、打开文件;
函数原型:
FILE*fopen(const char * filename,const char * openstyle)
(1)、第一个参数为要打开的文件名字
(2)、第二个参数为打开文件的方式
(3)、返回值类型为FILE* FILE是一个结构体(关于文件信息的数据包)
(4)、当打开文件失败时返回空指针NULL
打开方式:
r:可读,该文件必须存在
w:可写,先将文件长度截为0,如果文件不存在先创建
a:可写,向已有文件尾追加内容,不存在先创建
r+:可读可写
w+:可读可写
a+:可读可写
注意:使用任意一种“w”模式打开已有文件,文件内容将会被删除,以便程序以一个空文件开始操作
2、关闭文件
函数原型:
int fcolse(FILE*)
(1)参数为文件指针
(2)返回值 如果关闭成功返回0;否返回EOF
标准输入 stdin 键盘
标准输出 stdout 显示器
标准错误 stderr 显示器
重要
(1)printf:是把格式字符串输出到标准输出
(2)sprintf:是把格式字符串输出到指定字符串中,所以参数比printf多一个char*。那就是目标字符串地址
(3)fprintf:是把格式字符串输出到指定的文件设备中,所以参数比printf多一个文件指针FILE*
(1)scnaf:从控制台输入
(2)fscanf:从文件输入
(3)sscanf:从指定字符串输入
第八章:枚举、结构体、联合体
1、枚举类型
概念:c语言的枚举类型实质就是整形变量,只不过通过枚举类型将一类有关联的标识组合起来。
特点:增强程序的可读性和可维护性。
enum OS{iOS=1,Android=2,Wphone=3,Other=4};
enum weekday{mon,tue,wed,thr,fri,sat,sun} a ,b,c;
weekday为枚举类型名,a,b,c为枚举变量名。
(1)枚举类型的取值范围,是整数取值范围的一个子集。
(2)每个值对应一个整数,默认从0开始,依次加一。
枚举声明变量的方式;
(1)enum weekday
{
…….
};
enum weekday a,b,c;//a的数据类型就是enum weekday
(2)enum weekday
{
…….
}a,b,c;//定义类型的同时声明变量
(3)enum
{
……
}a,b,c;//定义类型同时声明变量,一般很少用
枚举类型的作用
(1)可以代替整形的宏定义,增强代码的可维护性。
(2)可用在switch语句中,作为常量使用。
(3)使用枚举变量时,应该把枚举变量的值赋值为枚举中常量中某个常量的值。
(4)c帮你缝装好一个define 集合,用便于记忆的字符来代表常量。
2、结构体类型
关键字:struct
概念:用来定义结构体的关键字。
作用:用已有类型的集合,定义一个新类型
struct
{
char fname[8];
char sname[16];
char exam[16];
char grade;
}record;//结构变量
定义结构体的形式
(1)直接定义结构体变量,结构体本身没有名字。
(2)用结构体类型名,声明一个结构体变量
(3)用typedef直接给没有结构体类型名的结构体类型,起一个别名。
4 、结构体访问
4、2指向结构体的指针
该结构体指针指向结构体的第一个成员地址,相当于一个多维指针
5、结构体赋值
(1)、
void * memcpy(void * dest,const void *src,size_t n);
功能:从源src所指的内存地址的起始位置开始拷贝n个字节到目标 dest所指的内存地址的起始位置中。
返回值:函数返回指向dest的指针。
总结:把一个结构体每个成员的值,拷贝给另一个结构体中对应的成员。
实例中:memcpy(&sam2,&sam1,sizeof(SAMPLE));
strcpy与memcpy的区别:
(1)复制的内容不同。strcpy只复制字符串,而memcpy可以复制任意内容,
例如字符数组、整型、结构体、类等。
(2)复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符‘\0’才结束,所以容易造成内容溢出。memcpy则是根据其第3个参数决定复制的长度。
(3)用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy。
6、结构体大小与内存对齐
每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)程序员可以通过
#pragma pack(n)其中n是“对齐系数”
内存分配法
数据对齐:是指数据所在的内存地址必须是该数据长度的整数倍
结构体的长度一定是最长的数据元素的整数倍
2、偏移量计算法
偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中的第一个成员的地址就是结构体变量的首地址,因此,第一个成员的偏移量基本都为0。
编译器在编译程序时的原则
(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍
总结:在设计结构体的时候,一般会遵照一个习惯,就是把占用空间小的类型排在前面,占用空间大的类型排在后面,这样可以相对节约一些对齐空间。
7、联合体
(1)联合体是一个结构
(2)他的所有成员相对于基地址的偏移量都为0
(3)此结构空间要大到足够容纳最“宽的成员”
(4)其对齐方式要适合其中所有的成员
定义联合体类型变量的一般形式
union 联合体名
{
成员列表;
变量表列;
}
联合体所占的内存长度等于最长的成员的长度
大端法:高位字节排放在内存低地址端,低位字节排放在内存高地址端
小端法:低位字节排放在内存低地址端,高位字节排放在内存高地址端
第九章预处理及程序执行过程/Users/admin/Downloads/软件执行过程.png
预处理:在编译之前,系统对源文件提前进行的内存处理。
指令 用途
# 空指令,无任何效果
#include 包含一个源代码文件
#define 定义宏
#undef 取消已定义的宏
#if 如果给定条件为真,则编译下面代码
#ifdef 如果宏已经定义,则编译下面代码
#ifndef 如果宏没有定义,则编译下面代码
#elif 如果前面的#if给定条件不为真,当前条件为真,则编译下面代码,相当于#elseif
#endif 结束一个#if.....#else...条件编译块
#error 指令将使编译器显示一条错误信息,然后停止编译
#line 指令可以改变编译器用来指出警告和错误信息的文件号和行号
#pragma 指令没有正式的定义,编译器可以自定义其用途,典型的用法是禁止或允许某些烦人的警告信息
第九章 预处理及程序执行过程
1、头文件包含
在编译之前,把头文件的内容,整个拷贝到当前源文件里。之后再进行编译。
#include
#include”stdio.h”告诉预处理程序在当前被编译的应用程序的源代码文件中搜索被包含的头文件,如果找不到,在搜索编译器自带的头文件。
2、宏替换
宏:是一种批量批处理上的称谓。宏是一种规则或模式,或称语法替换,用于说明某一特定输入(通常是字符串)如何根据定义的规则转换成对应的输出(通常也是字符串)。宏定义可以帮助我们防止出错,提高代码的可移植性和可读性。
语法:#define宏的名字(最好都用大写字母)宏将被替换的内容
2、2宏展开
宏展开也称宏替换,原理:在编译之前把宏的名字替换成真实的内容,宏————————只是作代码替换来用。这种替换在预编译是进行,称作宏展开。
例如:
#define PI 3.14
宏是文本,在替换时不会进行计算
面试题:提高了程序的运行效果减小运行时间,但是增大了我们对程序代码的扩展,
替换函数:
首先,写成宏定义
#define MAX(a,b)((a)>(b)?(a):(b))
其次,用函数实现
int max(int a,int b)
{
return (a>b a:b);
}
首先,函数调用会带来额外的开销,它需要开辟一片栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆栈。会降低代码效率,代码量也会大大增加。
其次,函数的参数必须被声明为一种特定的类型,所以它只能在类型合适的表达式上使用,我们如果要比较两个浮点型的大小,就不得不在写一个专门针对浮点型的比较函数。而宏是与类型无关的。
注意:替换函数时宏替换的参数都要加上括号
#define MAX(A,B) ((A)>(B)? (A):(B))//OK
#define MAX(A,B) (A>B? A:B)//ERROR
总结:当用宏去定义函数时,宏被替换的内容中的参数都需要加上括号()
2、3宏函数
替换自定义函数(除了最后大括号的结束行,其余都要加上“\”)
思考;有些任务根本无法用函数实现,但是用宏定义却很好实现,比如参数类型没法做为参数传递给函数,但是可以把参数类型传递给带参的宏。
3、条件包含
条件包含也称为“条件编译指令”或“条件预编译”。
4、程序执行过程
程序编译执行过程基本流为:预处理、编译、汇编、链接、运行。
第一:预处理
gcc -E hello.c-o hello.i
预处理后的.i文件不包含任何宏定义
第二:编译
gcc -S hello.i-o hello.s
编译之后生成.s文件,存储的为汇编代码
第三:汇编
gcc -c hello.s -o hello.o
此时存储的为二进制数据
第四:链接
每个.c文件经过上述步骤都生成了对应的目标文件(扩展名为.o或.obj)把生成的所有目标文件组装到一起的过程就是连接。