LINUX_C编程笔记
0.快捷操作:
①使用VI打开当前文件夹下所有文件: vi * -p
1.常用命令:
(1)exit命令, 所在库:stdlib.h eg:exit(0);
(2)fprintf(stderr,"***"); 输出错误信息到终端 所在库:errno.h eg:fprintf(stderr,"hello world");
(3)打印当前函数所在函数名以及行号: printf("%s,%d",__FUNCTION__,__LINE__);
(4)snprintf(tmp.name,NAMESIZE,"stu%d",i); //其中-> tmp.name:被写入的数组 , NAMESIZE:允许写入的最大数量 , “stu%d”,i : 写入的内容,类似printf
2.程序从编写到可执行,执行的过程:
C源文件--预处理---编译---汇编---链接---可执行文件
预处理: gcc -E hello.c > hello.i(以#开头的,头文件、宏定义等,都是在预处理过程解决掉) 预处理后,将结果重定向保存在hello.i
编译:gcc -S hello.i (生成了类汇编文件hello.s,如果认识汇编,这个文件可以看得懂)
汇编:gcc -c hello.s(生成hello.o)
链接:gcc hello.o -o hello
正常使用过程: gcc hello.c (默认编译选项,直接生成hello)
3.vim的配置文件是:~/.vimrc,该文件只对自己的用户有效。对所有用户有效的vim配置文件,是/etc/vimrc
4.查看某个关键字的帮助信息(man手册): 光标挪动到该关键字上,按shift + k ,直接可以跳转到man手册对应该关键字的内容
5.make工具的安装:①su root ②apt-get update ③apt-get install ubuntu-make
6.malloc返回值是一个void *,可以赋值给任何变量,任何变量也可以赋值给void *
7.gcc 使用过程中的一些知识点:
-Wall,打印所有警告
8.程序编写的规范:
①头文件包含的重要性
②return 0;
③多用空格空行
④添加注释
9.数据类型:
①char:半字节、8位
②short:2字节、16位
③int:4字节、32位
④long:4字节、32位
⑤float:4字节、32位
⑥double:8字节、64位
⑦float类型无法和另外一个值比较是否相等,因为永远不可能相等
10.宏定义的时候要注意一个问题,如果宏定义里有运算,一定要加括号,因为宏定义在预处理时,宏名会直接替换宏体.
eg: #define SUM 2+3 printf("%d",SUM*SUM); 在预处理时展会宏,会变成 printf("%d",2+3*2+3),打印出来的值是11,而非25
11.存储类型:
①auto :自动分配空间,自动释放空间
②static:静态型,自动初始化为0或者NULL,其值具有继承性(其修饰的变量无论程序怎么运行,只用一块地址空间,程序只定义一次)。还有一个用处就是定义一个全局变量时,如果在其他文件中出现相同变量,编译不通过,如果都用上static,就不冲突了,互相使用各自的不影响。即防止当前函数对外扩展。类似于C#中的public和private 。
③register:(建议型)寄存器型,寄存器数量少,运行速度非常快,变量是否会被放到寄存器,由gcc决定。只能定义局部变量,不能定义全局变量。只能定义32位大小的数据类型,如double就不可以。寄存器没有地址,所有一个寄存器类型的变量无法打印出地址查看
12.位运算的两个经典问题:
①将操作数中第N位置1:其他位不变,num = num | 1 << n ;
②将操作数中第N位置0:其他位不变, num = num & ~(1 << n);
13.输入、输出的几个经典问题:
(1)十六进制输出: int a = 255; printf("%x",a); --->输出ff
(2)单一字符输出: char a = 65; printf("%c", a); --->输出A
(3)字符串输出: printf("%s", "ABC"); --->输出ABC
(4)百分号本身的输出: printf("%%"); --->输出%
(5)双精度打印: double dou = 123.456; printf("%lf\n",dou);
(6)计算一年有多少秒:#define SEC_YEAR (60LL*60LL*24LL*365LL),这样表示,可以防止长度溢出
(7)printf打印机制:遇到'\n'强制刷新缓冲区输出,或者缓冲区满了输出,或者被强制输出缓冲区。
(8)scanf获取输入时,如果获取的为字符串,即%s,那么字符串不能出现空格、TAB等字符,否则会直接打印。
(9)使用scanf时,慎用%s,稍不细心,很容易越界。内存溢出。慎用gets(),稍不细心,很容易越界。内存溢出,可以用fgets,getline来替代
(10)为了防止scanf获取数值时错误,比如获取int型,输入了字符,需要校验返回值,如果返回值不等于1,则输入错误,做相应处理。
(11)printf的返回值是输出的字符数量,不包括最后的'\0'
(12)抑制符'%*c',用两个或两个以上scanf连续获取时,需要用抑制符占一个回车或者tab位,保证后续的scanf能正常获取。或者使用一个getchar()获取了这个换行或者TAB.
14.数组相关概念:
①二维数组行号可以省略,列号 绝对不可以省略;
②二维数组,指针加一,指针位置挪动一行;即跳转不是跳转1个,是跳转1行,可以理解位行指针;
③字符数组末尾会自动补一个'\0'。
15.指针相关概念:
①把指针指向a,把指针指向b,这里面改变的,是指针变量;
②指针变量和它所指向的值 的类型一定要相符。 如果指针变量定义成char,去取一个int型数据,那只会取出来一个char大小。结果极容易溢出。
③指针一旦定义出来,就要给一个明确指向。
④一道经典题: 假设i=1,i的地址=0x2000,p指向i,p的地址为0x3000,q指向p,q的地址为0x4000,那么:
q=0x3000=&p
&q=0x4000
*q=*(&p)=p=&i=0x2000
**q=*(&i)=i=1
想获取i的值,可以通过i直接访问,可以通过*p间接一次访问,可以通过**q间接二次访问。
一个二级指针所指向一级指针里面的内容,其实就是变量(一级指针所指向的变量)的值
⑤一维数组与指针的两个经典关系:
(1)可以用这么几种方式表示某一元素的值-> a[i] = *(a+i) = *(p+i) = p[i]
(2)可以用这么几种方式表示某一元素的地址-> &a[i] = a+i = p+i = &p[i]
其他1: sizeof(a[0]) 等于 sizeof(*a) 等于 sizeof(*(a+0))
其他2:能使用a的地方,就能使用p。那么两者的区别在于a是一个常量,p是一个变量。可以推出: p可以使用p++,a不可以使用a++
其他3:指针和数组,除了一个是常量,一个是变量,其余完全可以等价代换。
粗暴的理解为:带有*或者带有[]的就是值;带有&或者直接是指针名的就是地址。 eg: printf("%p -> %d" , p+i , *(p+i));
⑥二维数组的几个概念:
(1)二维数组的第一维参数可以理解为行变化,增加一次,就是移动一行;通过这个理解,可以认为一维数组是一个列变化,移动一次,跳过一个元素,是列在移动。
(2)二维数组 a[i][j] = *(*(a+i)+j)
(3)如何定义一个二维数组,即让一个指针指向二维数组: p = &a[0][0] p=*(a+0) p=*a
(4)数组指针: 如: int (*p)[3] = a ; 。指针移动一次,移动3个int,形象的理解为数组指针
数组指针的使用: 定义:int (*p)[4] = a; 使用: printf("%p -> %d %d %d %d",p,*p[0],*p[0]+1,*p[0]+2,*p[0]+3);
⑦数组指针的几个概念:
(1)char *str = "hello" (定义了一个字符指针),如果要赋值,不能使用如下方法: strcpy(str,"world"). (这句话的意思是把world替换到一个char常量上去) 要使用 str = "world"; (让指针放弃指向hello,让指针指向wolrd)
(2)char str[] = "hello" (定义了一个字符数组), 如果要赋值, 要使用strcpy(str,"world");
⑧指针与字符数组:
char str[]="hello"; 想改变str的内容: strcpy(str,"world");
char *str="hello"; 想改变str的内容: str = "world";
⑨const与指针
(1)const int *p / int const *p: 常量指针 ,指针的指向可以发生变化,但是不能通过指针来改指向的值。
(2)int *const p : 指针常量 ,指针的指向不能发生变化,可以通过指针来改指向的值
(3)如何区分指针常量和常量指针:先看到const,就是常量指针,先看到*,就是指针常量
如何区分俩种类分别保护谁: 先念谁,就保护谁。。。
⑩关于指针的几个大小总结:
int a[10]={1,2,3,4}; int *p=a; int *q=NULL; char *w=NULL;
那么: sizeof(*w) = 1 sizeof(*q) = 4 sizeof(*p) = 4 sizeof(p) =8 sizeof(int(*)) = 8 sizeof(char(*)) = 8 sizeof(int) = 4 sizeof(a) = 40
总结:*w被定义为char类型,所以其大小就是1个字节。
*q和*p被定义为int类型,无论其指向或者不指向,就是一个int的大小,所以其是4个字节。
p、int(*)、char(*)都是一个指针,对于64位系统来说,指针大小就是8个字节。
a的大小是整个数组的大小,因为a有10个元素,一个int占4个字节,所以a的大小就是40字节。
16.函数:
①一个进程的返回状态是给他的父进程看的;
②main函数入口参数的argv[]中的参数,末尾会自动补上NULL
③数组作为函数参数: 主函数: int a[] = {1,2,3,4,5}; print_arr(a,sizeof(a)/sizeof(*a)); 被调函数: void print_arr(int *p , int n) { }
传参类型: a *a a[0] &a[3] p[i] p *p p+1
接收类型: int* int int int * int int* int int *
④二维数组传递的方式: int a[3][4] ={1,2,3,4,5,6,7,8,9,10,11,12}; 被调函数: void printf_arr(int (*p)[N],int m,int n){ printf("%d", *(*(p+i)+j))};
传参类型: a[i][j] *(a+i)+j a[i]+j p[i] *p q[i][j] *q q p+3 q+2
接收类型: int int * int * int int int int* int(*)[N] int * int (*)[N]
⑤字符指针实现strcpy: char *mystrcpy(char *dest,const char *src){ char *ret = dest ; if(dest !=NULL && src!=NULL) while((*dest++ = *src++) != '\0'); } return ret;
⑥指针函数: int *fun(int);
函数指针: int (*p)(int);
函数指针数组: int (*arr[N])(int);
指向指针函数的函数指针数组: int *(*funcp[N])(int)
17.结构体
①结构体部分内容赋初值,可以使用如下办法: struct student_st stu = {.math = 98, .chinese = 96};
②结构体最大的特点是成员共存。
③共用体、位域的使用方法举例:
union
{
struct
{
char a:1;
char b:2;
char c:1;
}x;
int y;
};
18.动态内存管理:
①auto类型定义的在栈上,static类型定义的在静态区上,动态内存分配时从堆上取空间分配
②使用malloc()、free()之后,在free()之后,最好加一个p=NULL;可以避免指针乱指向,使用了未知空间。
③额外增加的一个知识点,typedef :给数据类型重新起名的 例如: typedef int INT;
19.makefile的一些知识:
①$@:上一行的目标文件
②$^:上一行的依赖文件
③%.o:%.c:所有的.o文件依赖于对应的.c文件
④OBGS=main.o tool.o //定义变量
⑤CFLAGS+= -c -Wall -g //追加定义
20.数据结构
①数据结构分类:线性、非线性 其中 -> 线性:线性表、栈、队列 非线性:
②存储形式:顺序存储(比如数组)、链式存储(比如链表)

浙公网安备 33010602011771号