嗨翻C语言笔记(二)
~a a中所有位都取反
a & b a中的位 与 b中的位 (都为1则1,否则为0)
a | b a中的位 或 b中的位 (只要对应位一个位1则为1)
a ^ b a中的位 亦或 b中的位
<< 位左移 (将某值乘于2的幂)
>> 位右移 (将某值除于2的幂)
@1.亦或:
比如 int a = 100;
int b = 88;
那么:
1100100 100
1011000 88
------------------
亦或 0111100 60 [只要对等位相同就取0,否则取1]
|(或) 1111100 124 [只要一个是1就去1,否则0]
& 1000000 64 [两个对等位都是1才取1,否则0]
printf("%d", 3 << 3); //即 3 * 2的三次方,即3*8=24
vs 21. 数据结构和动态存储
链表内容: 节点数据和链向下一条数据的链接.
在链表这种递归结构中不能省略结构名而直接用类型名的, 因为递归不允许用类型名来声明结构
假如要安排去游玩N个岛屿, 每个岛屿有岛名, 营业时间和关闭时间,还有下一个要去的island地址 四个元素.
typedef struct island {
char *name ;
char *opens;
char *closes;
struct island *next; //就是这里不允许用别名(类型名来,必须结构名,因此island结构名不能省略)
} island;
island taiwan = {"taiwan", "09:00", "17:00", NULL};
island hainan = {"hainan", "09:00", "17:00", NULL};
island jinmen = {"jinmen", "09:00", "17:00", NULL};
island mazu = {"mazu", "09:00", "17:00", NULL};
island hongkong = {"hongkong", "09:00", "17:00", NULL};
z在C语言中NULL的值实际上为0, 跟其他语言不一样地方
把他们链接起来
taiwan.next = &hainan;
hainan.next = &jinmen;
jinmen.next = &mazu;
通过修改指针的值,在链表中插入值,修改游玩路线比如在海南和金门间插入一个香港岛屿
hainan.next = &hongkong;
hongkong.next = &jinmen;
于是最终的路线是 taiwan->hainan->hongkong->jinmen->mazu
如果你有个很长的链表,假如你要使用第500个元素,你只能从头开始一个个读下去找.
display(&taiwan);
void display(island *start)
{
island *i = start; // 把其实地址赋给i, i就是起始岛屿的指针了
//如果i没有值即为NULL了就没有岛屿了
for (; i != NULL; i = i->next ) {
printf("name:%s\n open:%s;close:%s\n", i->name, i->opens, i->closes );
}
}
vs22.堆区动态存储
上面的链表岛屿是一开始就固定的,假如我要在旅途中自由选择下一站呢?即运行程序后随时增加岛屿, 这怎么整??? 继续存放在栈区肯定不行了, 执行完就给回收了, 必须要把他放在一个安全的地方 -> 堆空间,一旦申请堆空间,这块空间就不能再分配了, 除非你手动释放free()或中止程序, 否则你一直申请的话超过机器最大内存的话会发生内存泄露.
malloc() //申请内存,然后返回一个指向这块分配存储的指针.
//memery allocation 存储分配的意思
island *p = malloc(sizeof(island)); //获取island结构的大小来申请空间,然后返回地址保存在变量p中
strdup(); //用于把数组式的字符串内容复制到堆区去
char *s = "monday"; // 或者 char s[] = "monday";
char *copy = strdup(s): //把字符串内容复制掉堆区
实例:
动态选择的一个链表, 开业时间和关闭时间都是一样的, 不同在于岛屿名字和下一站岛屿.
代码如下
- gcc malloc.c -o malloc
- ./malloc < trip.txt
#malloc.c 内容如下
#include <stdio.h>
#include <stdlib.h>
typedef struct island {
char *name;
char *opens;
char *closes;
struct island *next;
} island;
island* create(char *name){
island *is = malloc(sizeof(island));
is->name = strdup(name);
//这里不写成 is->name = name 的原因是name是同一个数组传进来的地址, 所以每次都是用的同一个name地址,后面复制后会覆盖掉前面的name
is->opens = "09:00";
is->closes = "19:00";
is->next = NULL;
return is;
}
void display(island *start);
void release(island *start);
int main(){
island *start = NULL;
island *i = NULL;
island *next = NULL;
char name[80];
//i从NULL开始, 因此for()里的i=next就是上一个岛屿
for(; fgets(name,80, stdin) != NULL;i=next ) {
next = create(name);
//证明当前这个next是第一个岛屿,保存下它的数据来
if (start == NULL) {
start = next; //第一个岛屿
}
if( i != NULL ){
i->next = next;
}
}
display(start);
release(start);
}
void display(island *start){
island *i = start;
for(;i != NULL;i = i->next){
printf("name:%s\n,oepns:%s\n,closes:%s\n", i->name,i->opens,i->closes);
}
}
void release(island *start){
island *i = start;
for (; i != NULL; i=i->next){
free(i->name);
free(i);
}
}
[root@07 Cproject]# cat trip.txt 内容
taiwan
hainan
hongkong
jinmen
mazu
vs22. 函数指针 | char** 表示数组指针
比如 char *arr[] = { ... }; //比如这里arr 就是一个数组指针
<?php
function foobar($arg, $arg2) {
echo __FUNCTION__, " got $arg and $arg2\n";
}
class foo {
function bar($arg, $arg2) {
echo __METHOD__, " got $arg and $arg2\n";
}
}
// Call the foobar() function with 2 arguments
call_user_func_array("foobar", array("one", "two"));
// Call the $foo->bar() method with 2 arguments
$foo = new foo;
call_user_func_array(array($foo, "bar"), array("three", "four"));
-- 相信你本人对上面的php里函数调用函数的方式是很熟悉的? C怎么搞呢???
---
void* 指针可以指向任何数据类型.
const void * //前面加const, 表示这种类型不能修改,只能读取.
在C语言中, 函数名也是指针变量, 当你定义一个叫 int addTotal(int a){...} 函数的同时也会创建一个叫 addTotal 的指针变量, 当你在调用函数时, 你就是在使用函数指针:
addTotal(5); //你在使用函数指针
int addTotal(int a) {
return a++;
}
如何创建函数指针呢???
int (*func_name)(int);
func_name = addTotal;
func_name(4); //相当于调用addTotal(4);
声明语法: 返回类型 (*指针变量) (参数类型)
一旦声明了函数指针变量就可以向使用变量一样使用它,对它赋值并把它从函数传进去.
假如我有个计算过程, 需要使用上的addTotal()方法, 我就可以把它的函数指针传递进来使用了.
#include <stdio.h>
int calculation( int (*func_name)(int) ) {
int start = 5;
return func_name(start);
}
int addTotal(int a) {
return a++;
}
int main(){
//@1.声明一个函数指针
int (*func_name)(int) = addTotal;
printf("%d\n", calculation( func_name ) );
//@2.直接使用"&"
printf("%d\n", calculation( &addTotal ) );
//@3. 连"&" 也不使用
printf("%d\n", calculation( addTotal ) );
}
//为啥上面函数内"return func_name(start)" 语句中, 说func_name是指针了, 但为啥可以不加"*"星号取值为函数名而直接就可以使用呢??? 理应 *func_name(start) 的. --> 这是即使不加编译器也能识别这种情况.
甚至我都懒得通过上面 int (*func_name)(int) = addTotal; 的方式func_name函数指针传递进去, 而是直接使用 "&" 符号或不使用"&"符号, 获取函数指针地址:
calculation( &addTotal );
calculation( addTotal ); //这里你不加"&",编译器也可以根据calculation( int (*func_name)(int) ) 的定义知道这个参数是个函数指针.
@1. 是标准的函数指针声明过程.
@2, @3之所以也能成功, 关键是calculation()的参数定义是个函数指针.
vs 23. C语言标准库排序函数 qsort()
void* 指针可以指向任何数据类型.
const void * //前面加const, 表示这种类型不能修改,只能读取.
仔细看qsort()函数的定义:
qsort(void *array, size_t length, size_t item_size, int (*funcName)(const void *, const void *) );
params:
1.array : 表这是个数组指针, 用的是指针传参,因此返回的是排号序的数组,不创建新数组
2.length : 表示数组长度
3.item_size :表数组元素类型占的长度
4.int (*funcName)(const void *, const void *) : 函数指针,qsort函数使用来比较大小的, 因此函数指针的返回类型限死为int了.
void * 表任意类型指针, 加const表不能去修改参与比较的比较值.
既然规定了函数指针的返回值是int, 那么它的比较规则是啥???
只能返回3种整型值, 如果 a > b 返回整数, 等于返回0, 小于返回负数.
那根据参数比较函数该怎么定义呢? 首先根据函数指针的定义, 肯定是:
int 函数名(const void* argv1, const void* argv2) {
}
因此假设函数名是 compare_score, 用来比较分数:
int compare_score(const void* score_a, const void* score_b ) {
}
假设compare_score 函数是用来比较比较排序整型数组 的:
int scores[] = {5,4,1,3,2};
那么函数体内, 肯定要先把void* 指针转换为整数类型取出来, 比如
int a = *(int*)score_a;
int b = *(int*)score_b;
假设是用来比较字符数组的
char *name[] = {"hello", "world","good","night","today"};
那么函数体内, 首先要把 void* 指针转换为字符类型取出来:
char *a = *(char*)score_a;
char *b = *(char*)score_b;
再根据返回值只能返回整型, 且类型为整型,如果 a > b 返回整数, 等于返回0, 小于返回负数的比较规则. 因此一个比较整数数组元素的方法完整定义是:
int compare_score(const void* score_a, const void* score_b ) {
int a = *(int*)score_a;
int b = *(int*)score_b;
return a - b;
}
//调用
qsort(scores, 5, sizeof(int), compare_score);
vs 24. 函数指针应用
通常编码中大量遇到类似场景用switch判断某个值, 如果是1,case去执行1号函数,值为2,case执行2号函数,3去执行3号函数 ...
这种方式可行, 但改动起来麻烦而且累赘. 能够使用函数指针呢?
@1. 把值1,2...编入一个序号数组中,值是递进的, 那可以放枚举
@2. 再把1-N号函数编入一个指针数组中. 然后你的值就是函数指针数组的下标, 假如值为1,那么调用func[1] 对应的函数指针去执行.如此只需要在指针数组里不断注册函数指针,并且在序号数组中不断注册下标,于是即可完成上面的逻辑了.
怎么实现一个 函数的指针数组呢???
假如我有三个 void 返回类型的函数, 名为 dump(), second_chance(), marriage();
这种肯定行不通: replies[] = {dump, second_chance, marriage };
要想在数组中保存函数指针,就必须告诉编译器函数的具体特征, 函数返回什么类似哪个以及接收参数. (跟声明单个函数指针的逻辑其实一样的,只不过这次是一次性多个)
比如vs22中,声明一个函数指针的方式:
int (*funcName)(int) = addTotal; //addTotal是函数名
那么以此类推, 声明函数指针数组的方式为:
int (*replies[])(int) = { dump, second_chance,marriage};
假如我有个枚举排序数组, 那么 enum response_type {DUMP,SECOND_CHANCE,MARRIAGE};
那么 replies[SECOND_CHANCE] == second_chance; //指针等式
因此函数指针数组的声明语法为:
返回类型 (* 指针变量 ) ( 参数类型 )
因此完整的实现代码如下:
#include <stdio.h>
enum response_type {
DUMP,
SECOND_CHANCE,
MARRIAGE
};
typedef struct {
char *name;
enum response_type type;
} response;
void dump(response r){
printf("%d -- %s\n", r.type, r.name);
}
void second_chance(response r){
printf("%d -- %s\n", r.type, r.name);
}
void marriage(response r){
printf("%d -- %s\n", r.type, r.name);
}
int main() {
//实现一个根据人名指定回复的功能
//把所有人和对应的回复函数名放进一个response结构体数组里
response r[] = {
{"Mike", DUMP }, {"lucy", SECOND_CHANCE},
{"marry", MARRIAGE }, {"jerry",DUMP}
};
//参数类型为结构体 response
void (*replies[])(response) ={dump, second_chance,marriage};
int i;
for (i=0; i<4;i++) {
//第一个括号获取到指针,第二个括号是结构体
(replies[r[i].type])(r[i]);
};
return 0;
}
y因此下一次你新增回复类型和函数时只需在枚举和函数指针数组后追加新的即可:
比如新增LAW_SUIT回复类型和对应的函数law_suit:
enun response_type {DUMP, SECOND_CHANCE,MARRIAGE, LAW_SUIT};
void (*replies[])(response) ={dump, second_chance,marriage,law_suit};
vs 25. 静态库与动态库
-I 指定路径寻找头文件
-L 指定静态存档目录
-l 紧挨存档名,表打开这个存档
-c 创建目标文件但不要链接它
标准头文件一般放下面的目录:
/usr/local/include [一般第三方库头文件]
/usr/include [一般系统库头文件]
@1. 共享头文件
a.放上面 /usr/local/include 目录了
b.include 后接绝对地址的路径
c.编译时 用 -I 指定路径去找
比如:
gcc -I /myHeaderFile test.c -o test; //让编译器同时在 /myHeaderFile路径和标准目录下找
如果要共享 .o 目标文件的话编译的时候在目标文件前面加上绝对路径
gcc -I /myHeaderFile test.c /myObjFile/encrypt.o /myObjFile/checksum.o -o test;
PS: 记得目标文件 ".o" 要写在源文件 ".c" 后面.
上面我要共享两个".o" 文件就要在编译的时候把两个的路径都列出来, 共享10个就列10次, 这不行啊???
创建目标文件存档 -> 一次性告诉编译器一批目标文件. (即把N个目标文件打包在一起,类似于zip把N个文件压缩在一起, 或者java的.jar包)
比如通过 find /usr -name "*.a"
搜索出一大堆 ".a" 文件来, 选取其中一个, 用nm命名了查看存档文件
[root@07 sys]# nm /usr/lib64/mysql/libmysqlservices.a
service_command.c.o:
0000000000000000 D command_service
locking_service.c.o:
0000000000000000 D mysql_locking_service
srv_session_service.c.o:
0000000000000000 D srv_session_service
my_snprintf_service.c.o:
0000000000000000 D my_snprintf_service
//...
如上, 这是一个叫 libmysqlservices.a文件的存档清单, 记录了这个存档由那些 ".o" 目标文件构成:
### 创建存档:
用 ar 命令.
ar -rcs ./libs/libhfsecurity.a encrypt.o checksum.o
表将创建好的存档文件放到当前目录下的libs/ 目录里
-r表示存档.a若存在则更新
s 表示要在.a文件开头建立索引
c 表示创建时不信啊是反馈信息
libmysqlservices.a 表要创建以lib开头的的 .a 文件名
encrypt.o;checksum.o 表保存在存档中的目标文件
存档是静态库以lib开头做标识, 因此真正的存档名应该是 hfsecurity
存档的保存目录:
/usr/local/lib 标准库目录,不是root可能塞不进去
自定义目录. 比如 /myLib
#### 做好存档好就可以编译程序了.
-l表打开这个存档
gcc test.c -lhfsecurity -o test
-l紧跟着存档名, 如果是多个存档则多个 -l接存档名
-lhfsecurity 表让编译器去找一个叫 libhfsecurity.a 的存档.
假如你把存档放在了自定义目录 /myLib 中, 那么用 -L指定
gcc test.c -L /mylib -lhfsecurity -o test
假如你想把 存档里的 encrypt.o 提取出来一份/复制 只需使用ar -x 命令
ar -x libhelloAdd.a encrypt.o
然后就会看到目录下有一个encrypt.o 文件了
#### ar -t <文件名> 可以列出存档里的目标文件( nm <文件名> 也可以 )
[root@07 Cproject]# ar -t libhelloAdd.a
hello.o
addFunc.o
实例:
健身房静态库的创建和调用的完整结构如下:
-rwxr-xr-x. 1 root root 8592 May 31 23:15 elliptical
-rw-r--r--. 1 root root 100 May 31 23:01 elliptical.c
-rw-r--r--. 1 root root 1584 May 31 23:04 elliptical.o
-rw-r--r--. 1 root root 179 May 31 22:59 hfcal.c
-rw-r--r--. 1 root root 1576 May 31 23:10 hfcal.o
drwxr-xr-x. 1 root root 102 May 31 23:01 includes
drwxr-xr-x. 1 root root 102 May 31 23:11 libs
@1. ./includes/ 目录下有hfcal.h 文件, 仅有一段声明:
void display_calories(float weight,float distance, float coeff);
@2.然后是hfcal.c 里边就是display_calories()函数:
#include <stdio.h>
#include <hfcal.h>
void display_calories(float weight, float distance, float coeff) {
printf("weight=%f;distance=%f;coeff=%f\n", weight, distance, coeff);
}
@3. 最后是elliptical.c 源文件, 内容如下:
#include <stdio.h>
#include <hfcal.h>
void display_calories(float weight, float distance, float coeff) {
printf("weight=%f kg;distance=%f km;coeff=%f\n", weight, distance, coeff);
}
[root@07 calories]# cat elliptical.c
#include <stdio.h>
#include <hfcal.h>
int main(){
display_calories(115.2,11.3,0.79);
return 0;
}
1. 首先串讲一个hfcal.o目标文件, hfcal.h头文件保存在了./includes 中了
gcc -I ./includes/ -c hfcal.c -o hfcal.o
2. 编译elliptical.c的目标文件elliptical.o
gcc -I ./includes/ -c elliptical.c -o elliptical.o
3. 用hfcal.o创建存档库, 并保存到 ./libs 目录里
ar -rcs ./libs/libhfcal.a hfcal.o
4. 用elliptical.o 和 hfcal存档创建elliptical可执行文件
gcc elliptical.o -L ./libs/ -lhfcal -o elliptical
5. 最终执行
[root@07 calories]# ./elliptical
weight=115.199997 kg;distance=11.300000 km;coeff=0.790000
小尾巴 - 动态库
至此顺利完成了健身房的问题, 但小尾巴是 假如健身房 业务扩大, 在多个国家都有服务,
而weight在美国的器材显示单位中是pound, distance的单位在美国是mile, 而在俄罗斯 ...
总之这个display_calories()方法在不同国家有不同的显示方式.
你不可能写出一套适应所有国家的显示的代码, 因为他们在编译时hfcal.o和elliptical可执行文件就静态链接在一起了,该咋办???
--> 假如我仍旧是编译了elliptical可执行文件,只不过此时 hfcal.o 不是跟我黏在一起, 而是把目标代码作为一个可选的独立组件存在, 我可以在程序运行时针对不同国家自由链接不同 hfcal.o 的目标文件内容, 做到热插拔的话, 那就爽死了. --> 由此引出 动态库.
可以把目标代码分别保存在单独的文件中, 在程序运行时才把他们动态链接到一起
@1. 创建与位置无关的目标文件
gcc -I ./includes -fPIC -c hfcal.c -o hfcal.o
-fPIC 表示创建与位置无关代码. [即无论在哪个位置都可以加载]
win 下动态库叫动态链接库, 后缀名是 .dll [win下php扩展名]
linu想下叫共享文件, 后缀名是 .so [php扩展很多这样的]
mac 上交动态库, 后缀名为 .dylib.
@2. 虽然各个平台下叫法不一,但创建方法相同.
|-- ./libs/libhfcal.dll
gcc -shared hfcal.o -o --|-- ./libs/libhfcal.so
|-- ./libs/libhfcal.dylib
--shared 选项告诉gcc想把.o目标文件创建为动态库
假如你在linux上创建了libhfcal.so的库, 那么libhfcal.so就会记住它的库名叫hfcal,因此不能改名,要改名得重新编译[php的文件名和类名保存一直概念]
@3. 编译使用
gcc -I ./includes -c elliptical.c -o elliptical.o
gcc elliptical.o -L ./libs -lhfcal -o elliptical
和静态库编译的命令一样,但动态库不会再可执行文件里包含动态库的代码,而是插入一段代码用来查找动态库,并在运行时链接. 比如php必须在php里加入 extension=/libs/mysql.so 代码来链接这个动态库. 因此这里还要手动加载下
@4.设置LD_LIBRARY_PATH 常量
1.手动加载绝对路径让找
2.放在标准目录下 /usr/lib 直接可加载. [linux]
3.设置环境变量让找库目录. LD_LIBRARY_PATH
如果不在/usr/lib 目录下必须设置库目录的路径LD_LIBRARY_PATH, 否则运行二进制报下面的错误:
[root@07 dynastic]# ./elliptical
./elliptical: error while loading shared libraries: libhfcal.so: cannot open shared object file: No such file or directory
然后:
LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/youdirs
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/youdirs
设置好后再运行就正常了:
[root@07 dynastic]# ./elliptical
weight=115.199997;distance=11.300000;coeff=0.790000
因此现在你在全球的业务可以把 hfcal.c 做出相应改动后编译成 hfcal_CN.so, hfcal_UK.so ... 实现热插拔, 满足不同需求.
进程间通信
0 - 标准输入(键盘)
1 - 标准输出(比如屏幕)
2 - 异常输出(比如屏幕)
./myprog 2>&1
2 > 表标准异常, 2 > &1 表示异常错误信息也链接输出到标准输出去
预处理相关:
define 用于定义宏(常量)
#define DAYS_OF_THE_WEEK 7
条件编译:
#ifdef DAYS_OF_THE_WEEK
char *greeting = "sunday";
#else
char *greeting = "monday";
#endif
static 关键字
传统情况下你要在函数内修改某变量值(做计算器统计),一般这个值是全局变量,否则你无法在函数内访问(指针另算);
比如
int count = 0;
int counter() {
return ++count;
}
但问题是这种全局变量定义太多在大项目中很容易被覆盖或啥的,危险, 最好能创建只在函数中访问的全局变量.
int counter(){
static int count = 0; //第一次访问会初始化这个,下一次就不会再初始化这条语句了
return ++count;
}
用static 定义的私有变量或函数. [都只能在本.c源文件中使用]
static int func(){ //... }