1、C语言的精髓–指针
指针就是地址,在一定程度上可以把数组名就看成一个特殊的指针,指针也就是一个变量而已,指针全名指针变量。
(1)指针数组
指针数组,是个数组,里边放的东西都是指针。char *p[2]={"china","linux"};
仔细来说,从运算符优先级来看,[]的优先级比*高,所以p先和[]结合。所以这就是个数组,再与指针结合,所以叫指针数组。 二重指针可以指向指针数组。
(2)数组指针
数组指针,是个指针,指向数组的指针.
1 | |
这里指针是在()中放着,())和[]优先级都是最高的,结合性是从左向右,所以先与*结合,这是个指针,然后与[]结合,就是指针数组。
(3)函数指针
1 | |
void *类型可以指向任何一个类型的指针
void (*signal(int sig, void (*func) (int))) (int)
signal仍然是一个函数,他返回一个函数指针,这个指针指向的函数没有返回值,只有一个int类型的参数
(4)二维数组
a[5][7];//用指针访问就是*(*(p+5)+7) int (*p)[7]=a;
(5)字符数组与指向字符串指针的问题
其中char *p="linux" 这种情况字符串Linux只存在于只读数据段中(rodata),所以p所指向的内容不可以被更改,如*(p+1)=a;这是实现不了的,但是可以更改指针p 指向的地址,例如p=”mengchao”,但是这里的p=”mengchao”这段话放在子函数中是不可以的,字符串mengchao是存在于栈中,子函数结束就被释放了,不能够达到改变的目的。可以使用二重指针来实现。
char p[]=”linux” 这个是字符数组,相当于初始化数组,可以更改内容,如p[1]=’a’;* 这里Linux存在于栈上
所以有的字符串操作函数例如char *strcat(char *dst,char const *src);前面的参数dst,需要修改,只能传数组,不能直接传一个字符串,因为字符串不可改变
所以想修改字符串,请将他放在字符数组中。 另外关于这个程序
1 | |
这里想插一句关于strlen和sizeof的区别。比如我们定义char buf[100]={“helloworld”}; strlen(buf);就是里边字符串的长度,而sizeof(buf);就是数组长度100.
一定程度上可以认为一级指针与一维数组名等价,二级指针与指针数组名等价,数组指针与二维数组名等价。而二级指针和二维数组名没有一毛钱关系。
(6)数组名
数组名可以看成是首元素的首地址,也可以当成一个整体来看。数组名是常量,才可以唯一的确定数组元素的起始地址。设有一维数组int a[5]:
a[1] = *(a+1); //a代表首元素首地址,加1跨度为int大小为4
对一维数组名和二维数组名引用(取地址)对一维数组名进行引用会将使其升级为二维数组名。
(&a+1)//这个东西加1,加的大小是整个数组的大小,是20.
二维数组名解引用,降维为一维数组名。二维数组名是首元素首地址,设有int a[4][3],则
*(a+1) //代表的是二维数组的第二维 a[1][2] = *(*(a+1)+2)
注意下面的问题
1 | |
2、进程空间

1 | |
3、结构体
《道德经》 曰:”一生二,二生三,三生万物”。结构体用关键字 struct 定义,表达出多个不同变量在一起的类型。我们由此可以使用struct自定义几乎所有想要的类型。
结构体的定义:
1 | |
这种定义出来的结构体类型在定义变量时必须跟着struct,就像struct student stu1;一样,我们引入typedef,避免这种情况。
1 | |
结构体变量初始化及成员访问
定义的结构体变量用点成员运算符(.)访问成员变量,比如stu.sex就这样访问成员变量。如果是结构体类型的指针变量则使用->访问成员变量。STUDENT stu; stu->sex = 'x';初始化大概有三种方法。
举例:
1 | |
方法一:
1 | |
方法二:
1 | |
方法三:
1 | |
结构体大小
设计内存对齐
1 | |
结构体所占的内存大小 a.整体所占的内存大小应该是结构中成员类型最大的整数倍,此处最大的类型是int_64t,占8个字节。即最后所占字节的总数应该是8的倍数,不足的补足 b.数据对齐原则-内存按结构体成员的先后顺序排列,当排到该成员变量时,其前面所有成员已经占用的空间大小必须是该成员类型大小的整数倍,如果不够,则前面的成员占用的空间要补齐,使之成为当前成员类型的整数倍。假设是地址是从0开始,结构体中第一个成员类型char型占一个字节,则内存地址0-1,第二成员从2开始,int型所占内存4个字节根据原则b,第一个成员所占内存补齐4的倍数,故第一个成员所占内存:1+3=4;第二个成员占5-8.第三个成员占8个字节,满足原则b,不需要补齐,占9-16第四个成员占一个字节,占17.故总内存为1+3+4+8+1=17个字节,但根据原则1总字节数需是8的倍数,需将17补齐到24.故此结构体总字节数为:24字节
4、一些关键字的详解
inline
在c/c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了inline修饰符,表示为内联函数。栈空间就是指放置程序的局部数据(也就是函数内数据)的内存空间。 inline的使用是有所限制的,inline只适合涵数体内代码简单的涵数使用,不能包含复杂的结构控制语句例如while、switch,并且不能内联函数本身不能是直接递归函数(即,自己内部还调用自己的函数)。 inline函数仅仅是一个对编译器的建议,所以最后能否真正内联,看编译器的意思,它如果认为函数不复杂,能在调用点展开,就会真正内联,并不是说声明了内联就会内联,声明内联只是一个建议而已。
inline函数的定义放在头文件中
其次,因为内联函数要在调用点展开,所以编译器必须随处可见内联函数的定义,要不然就成了非内联函数的调用了。所以,这要求每个调用了内联函数的文件都出现了该内联函数的定义。 因此,将内联函数的定义放在头文件里实现是合适的,省却你为每个文件实现一次的麻烦。 声明跟定义要一致:如果在每个文件里都实现一次该内联函数的话,那么,最好保证每个定义都是一样的,否则,将会引起未定义的行为。如果不是每个文件里的定义都一样,那么,编译器展开的是哪一个,那要看具体的编译器而定。所以,最好将内联函数定义放在头文件中。
注:之所以定义放在头文件中,是因为inline是在编译阶段展开的,并不是连接源文件。
static
static别的东西不想说,就说static在修饰局部变量的时候,函数结束此时局部变量并不会被释放,并且修饰的局部变量的值是一直保存的,类似于全局变量,但是还是只能在这个函数内才能访问
const
const又叫常变量,修饰的变量一般不能被更改。但是在C语言中这是假的,通过定义一个指针,修改指针指向的内存依旧可以更改const修饰的变量。在C++中才是真正的const,无法修改。 const一般出现在参数列表中,意味着这个变量在函数中并不会被修改,是输入型参数。
volatile
这个volatile,英文名易变的。因为访问寄存器要比访问内存单元快的多,所以编译器一般都会作减少存取内存的优化,但有可能会读脏数据。当要求使用volatile声明变量值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据。 精确地说就是,遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问;如果不使用valatile,则编译器将对所声明的语句进行优化。
5、多文件编程
一个简单的例子 全局变量最好声明为 static,只在当前文件中可见,不要对外暴露;如果必须对外暴露,可以使用宏定义代替,全局变量最好只在本文件内使用,如果其他文件必须使用,最好封装get接口set接口,请看下面的代码。 main.c 源码:
1 | |
module.c 源码:如果使用了头文件中的宏定义,这里也是需要包含头文件的
1 | |
module.h 源码:
1 | |
多个.c文件的时候,也可以只用一个.h头文件,然后都包含一下这个.h文件
6、文件操作
https://www.cnblogs.com/wuqianling/p/5340719.html这篇文章写的就不错。
主要是文件操作分为二进制文件和文本文件,fopen打开的时候设置打开模式,返回值问FILE*类型的一个文件指针。通过这个文件指针就可以访问这个文件。
7、编码规范
- for while等关键字和括号之间留出一个空格,突出关键字,for后哪怕只有一行也写{}
- 注释的双斜线之后要有一个空格,突出注释
- 函数名和类使用大驼峰命名法首个单词大写,变量使用小驼峰命名法第一个字母小写
- 类和函数写完之后要空一行
- 分号、逗号前边不空格,后边留空格。双目运算符两边留空格