1、为什么会选择这套开发环境,是如何搭建的?
1、与windows的集成开发环境相比,在Liunx系统下通过终端学习C语言能够更深入地了解C语言的细节
2、Vitrual Box 开源并且足够满足学习C/C++
3、Ubuntu 16.04 LTS 长期支持版本 简易容易上手
2、你用的Linux命令有哪些
常用: ls cd pwd whoami clear man size(代码段、数据段、bss段) time strace
进程的内存分布情况就是所谓的进程映像,从低地址到高地址依次分布为:
text 代码段 二进制指令、常量(字符串字面值、被const修饰过的原data的数据)
data 数据段 初始化过的全局变量和静态局部变量
bss 静态数据段 未初始化过的全局变量和静态局部变量 程序运行前,会自动清0
heap 堆 由程序员手动管理的体量较大的数据
stack 栈 局部变量、块变量
environ 环境变量表 环境变量表 环境变量 每个进程都有一份,修改也不会影响其它进程
argv 命令行参数 程序运行前命令附加的参数
文件: touch rm mv cp cat/more/head/tail
目录: mkdir rmdir cp -rf rm -rf
网络: ifconfig/ipconfig ping ssh telent ftp+
其他: ps -aux chmod find tar reboot ln
3、vim文本编辑器的常用操作
替换(:%s/old/new) 查找(:/key) 自动补全(插入:Ctrl+p) 缩进(:> n / :< n) 执行命令(:!cmd)
自定义:
快速保存退出 快速保存并编译运行 自动添加main函数、头文件
4、gcc编译器常用的编译参数
-E 预处理
-S 编译
-c 只编译不链接
-o 指定编译结果的名字
-Wall 尽可能多的产生警告
-Werror 警告当错误处理
-I 指定头文件加载路径
-l 指定要使用的库名
-L 指定库文件的加载路径
-D 编译时定义宏
-g 添加调试信息 gdb调试使用
-fpic 生成与位置无关代码 .so
-static 优先使用静态库
-std 指定编译语法标准
5、C代码是如何变成可执行程序的
1、预处理
-E 生成.i结尾的预处理文件
2、编译
-S 生成.s结尾的汇编文件
3、汇编
-c 生成.o结尾的目标文件(二进制exe elf)
4、链接
把若干个目标文件、库文件合并生成可执行文件
6、long字节数、char、short的取值范围
long 、 指针 4或者8 系统位数32\64
-128~127 0~255 共256个数
-32768~32767 0~65535 共65536个数
7、int、float、指针、bool类型数据与"零值"比较判断
if(0 == num)
if(0.000001 > float && -0.000001 < float)
if(NULL == p)
if(flag)
8、C语言有哪些类型限定符,分别什么功能?
const 保护变量不被显示地修改
static 改变存储位置、延长生命周期、限制作用范围
typedef 类型重定义
volatile 取消编译器对变量地取值优化过程
register 申请变量存储到寄存器
auto 定义自动分配、释放地变量(局部变量)
extern 声明变量,在使用其他.c文件中的全部变量
9、与指针相关的知识点有哪些
1、什么是指针
指针是一个数据类型,使用它可以定义指针变量,指针变量存储的是无符号整型数据,代表了内存的编号,每个编号对应1字节的内存
通过这个编号可以访问对应的内存(指针变量可以通过解引用运算符访问它所指向的内存) 具体访问多少字节由它的类型决定
2、什么情况下用指针
函数之间共享变量(返回多个参数)
提高传参效率(一般与const配合)
配合堆内存使用
注意:除了这三种情况,不要使用指针
3、使用指针时需要注意哪些问题
空指针: NULL 解引用会段错误 使用来历不明的指针之前先判断后解引用
野指针:指向不确定内存的指针
一切正常、段错误、脏数据
危害比空指针大 避免产生野指针
4、指针的运算
指针+整数 指针+指针的类型宽度*n 《=》前进n个元素宽度
指针-整数 指针-指针的类型宽度*n 《=》后退n个元素宽度
指针-指针 (类型相同) (指针-指针)/指针类型宽度 计算两个指针之间间隔了多少个指针元
5、指针与const (就近原则)
const int* p; //保护指针所指向的内存不被修改
int const* p;
int* const p; //保护指针变量不被修改
cosnt int* const p; //指针变量和指针所指向内存都不能修改
int const * const p;
6、数组指针与指针数组
指针 和 数组
数组指针是专门指向数组的指针
int (*arrp)[10] = malloc(sizeof(int)*10*5) 5行10列
指针数组 成员是指针的数组
int* arr[10];
for(int i=10; i<10; i++)
{
arr[i] = maloc(sizeof(int)*m); }
7、void指针的作用
它能与任意类型的指针自动转换
当一个函数的实参可以是任意类型时,那么形参就需要定义为void*类型
当函数返回值为指针类型,且不确定用什么类型指针接收,可以返回void*
万能指针
qsort cmp(void*,void*)
malloc
C++ 有模板 引用 void*受限制
8、二级指针
指向指针的指针 专门用于存储指针变量的地址
跨函数共享指针变量时传递二级指针
9、函数指针
专门指向函数的指针
函数会被编译成二进制指令存放在代码段中,函数名就是它在代码段中的首地址
函数指针是存储了该函数的内存地址,可以把指针像函数传参一样进行使用
回调模式 qsort、atexit\on_exit\cmp()
typedef int (*funcp)(int,double);
funcp(10,2,33);
funcp fp(10,2.33);
加与不加typedef的区别
问: 在main()前执行其他函数代码
C不行 C++行
10、结构指针
指向结构的指针 -> 访问成员
传参 提高传参效率 栈内存太小 配合堆内存存放结构变量
11、结构中成员指针
一般不建议使用
要记得给该结构成员指针分配内存,然后还要给该结构成员指针分配内存
深拷贝
1、否则会产生重复释放
2、保存结构变量到文件中时,要存储成员指针所指向的内存的数据,而不是简单保存该指针变量的值
否则当重新读取文件中该地址时,这个地址就失效
12、指针与数组名的相同点、不同点
指针 变量 指针变量是拥有自己的存储空间,它与所指向的内存是指向关系
数组名 常量 数组名是一个常量,不能修改它的值,数组名没有自己的存储空间,它与数组首地址之间是映射关系
数组名[i] == *(数组名+i)
*(p+i) == p[i]
都代表了内存编号 都可以使用[]访问 *解引用
注意:当数组作为函数的参数时蜕变成了指针,所以长度丢失
10、结构、联合的对齐补齐
编译器是为了提高访问的结构、联合的成员内存的速度,会在成员之间填充一些空白无用的字节
对齐:
假设第一个成员使用0地址,那么接下来每个成员说使用的内存地址编号必须是它类型字节数的整数倍
否则填充一些空白字来达到对齐
补齐:
结构、联合的总字节数必须是它最大成员的类型字节数的整数倍,否则在末尾填充空白字节来达到补齐
在Linux系统中计算对齐、补齐时字节数超过4字节按4字节算
在windows中按照实际算
可以手动设置对齐补齐的最大字节数 #pragma pack(n) n只能时 1 2 4
11、如何判断系统的大小端
小端:高位地址存储高位数据,低位数据存储低位数据
Linux、Windows等个人PC系统采用小端,因此小端字节序也叫做本地字节序
大端:高位地址存储低位数据,低位地址存储高位数据
网络设备、UNIX服务器采用大端,因此大端字节序也叫做网络字节序
注意:本地数据传输到网络,可以进行大小端转换,还可以把数据转换成字符串后发送
union Data{
int num;
char ch;
};
union Data d;
d.num = 0x01020304;
if(0x04 == d.ch) xiao
else da
12、预处理指令有哪些,功能?
#include <>/""
#define
二义性:当使用宏的环境发生变化,宏替换后的运算规则也随着改变,这种现象称为宏的二义性,应该去避免这种情况
为宏的每个参数加上小括号,再给宏整体加上小括号,不要在宏中使用自变运算符(要会进行宏替换)
#define 和 typedef的区别
替换 类型重定义
#define INTP int*
typedef int* INTP;
INTP p1,p2,p3;
宏常量与const全局变量的区别
存储位置
代码段
data、bss
#define num 10 会报错
const int num = 10; 屏蔽const的全局变量,再定义一个局部变量
int main()
{
int num = 10;
}
宏函数和内联函数的优缺点
相同点:
提高了代码的运行速度,使代码变得通用。
不同点:
1、内联函数是在编译时展开,而宏在预编译时展开;
在编译的时候,内联函数直接把函数的二进制指令替换调用语句,而宏只是一个简单的文本替换。
2、内联函数可以进行诸如类型安全检查、语句是否正确等编译功能,宏不具有这样的功能;宏不是函数,而inline是函数。
3、宏在定义时要小心处理宏参数,一般用括号括起来,否则容易出现二义性。而内联函数不会出现二义性。
4、inline有点类似于宏定义,但是它和宏定义不同的是,宏定义只是简单的文本替换,是在预编译阶段进行的。
而inline的引入正是为了取消这种复杂的宏定义的。
#ifndef/#define/#endif 防止头文件被重复包含
头文件的相互包含: xxx未定义
a.h中包含了b.h的内容
b.h中包含了a.h的内容
提取冲突部分,写入新的c.h中 让a.h b.h去包含c.h
13、堆内存与栈内存的优缺点
栈内存优点:
方便: 自动申请、释放
安全:不会产生内存泄漏、内存碎片
栈内存缺点:
大小受限,不同系统版本还不一样,但是可以修改
释放时间不可控,数据想要长时间使用就不能存储再栈内存
堆内存的优点:
足够大,理论上可以使用物理内存的极限
释放时间受控,可以根据实际情况,随时释放内存
堆内存缺点:
使用麻烦, malloc\free 手动申请释放
危险性高,可能会产生内存泄漏以及内存碎片
14、二进制文件与文本文件的区别
二进制文件: 把内存中数据的补码直接存储到文件中
直接存储、直接读取、方便快捷、无需受类型影响
但是无法通过文本编辑器直接阅读查看
文本文件:把数据转换成字符,再把字符的二进制存储到文件中、
先转换成字符串存储 读取时还需要解析成对应的类型
可以通过文本编辑器直接阅读查看文件内容
注意: 如果在windows下操作文件一定要参数 "rb" ,否则会出错
windows的文本文件写入 "\n" 变成 "\r\n"
Linux去读取window的文件,数据会被抹掉
15、原码、反码、补码的概念和转换,进制转换?
原码:数据的二进制
反码:正数反码就是它的原码,负数的反码就是它原码的除符号位外,其他位按位求反
补码:正数补码就是它的原码,负数的补码就是它的反码+1
注意:内存中所有数据的存储都是以补码形式存储的
1、负数转换成二进制
2、符号位不变,其余按位求反,得到反码
3、反码+1得到补码
-127
原码: 1111 1111
反码: 1000 0000
补码: 1000 0001
1 + (-1) 得用补码计算
0000 0001
1111 1111
1000 0000
补码怎么转数据:
无符号补码直接转换成十进制
有符号最高位是0,说明是正数,也直接转换成十进制
有符号且最高位是1:
1、补码-1得到反码
2、符号位不变,其他位按位求反得到原码
3、原码转换成十进制数据
1111 1111 补码 有符号
1111 1110 反码
1000 0001 原码
-1 十进制
16、什么是内存泄漏,如何定位内存泄漏?
由于程序员的粗心大意或者业务逻辑问题,导致已经使用完毕的内存没有及时释放
当再次使用时又需要重新申请,又没有释放,长期以往,导致能用的内存越来越少,直至系统崩溃
1、通过windows任务管理器、Linux的ps -aux 查看 GDB也可以查看内存情况
2、借助代码分析工具 matrace 分析malloc free的使用情况
3、封装malloc free 记录调用情况到日志
17、什么是内存碎片,如何减少内存碎片?
已经释放了,但是又无法使用的内存,由于申请、释放时间、大小不协调,几乎无法避免
1、尽量申请大段内存、自己管理,使用时间尽量长
2、尽可能使用栈内存
18、什么是IO缓冲区,需要注意什么问题?
输出缓冲区:
刷新输出缓冲区:
1、遇到\n
2、遇到输入语句
3、程序结束
4、输出缓冲区满
5、fflush(stdout)
输入缓冲区:
1、先输入整数、浮点型,然后输入字符、字符串时,缓冲区中有 '\n'残留影响输入
2、fgets函数超出size,只接受size-1,超出部分会在输入缓冲区残留,影响接下来的输入
3、要求输入整型、浮点型,但是实际输入字符型,输入失败,并且继续残留在缓冲区
19、strlen、strcpy、strcat、strcmp 能实现背下来
明白原理 '\0'
"hehe\0hehe" 4
"abc\0" "bbbbbbb\0"
strlen
size_t to_len(const char* str)
{
const char* temp = str;
while(*temp) temp++;
return temp - str;
}
strcpy
char *copy(char *dest,const char *src)
{
char* temp = dest;
while(*temp++ = *src++);
return dest;
}
strcat
char *cat(char *dest,const char *src)
{
char* temp = dest;
while(*temp) temp++;
while(*temp++ = *src++);
return dest;
}
strcmp
int cmp(const char *s1,const char *s2)
{
while(*s1 == *s2 && *s1) s1++,s2++;
return *s1-*s2;
}