基础
为甚么学C
主要是因为最近在学习nginx
这货是用C写的,如果想自己写一个模块,并编译到nginx当中,那么C必不可少
helloworld
在虚拟机192.168.27.131上的/home/solofeng/study/repos/C下开展国际惯例
[solofeng@com C]$ vi helloworld.c
==========================
#include <stdio.h>
int main(){
printf("hello world! \n");
return 0;
}
==========================
[solofeng@com C]$ gcc helloworld.c -o helloworld /*编译*/
[solofeng@com C]$ ls
helloworld helloworld.c
[solofeng@com C]$ ./helloworld /*执行*/
hello world!
关于C中的格式化输出的%
实际上在输出中是作占位符的作用
格式说明由“%”和格式字符组成,如%d%f等。它的作用是将输出的数据转换为指定的格式输出。格式说明总是由“%”字符开始的。
格式字符有d,o,x,u,c,s,f,e,g等。
如
%d digit 整型输出,%ld长整型输出,
%o octonary 以八进制数形式输出整数,
%x hexadecimal 以十六进制数形式输出整数,或输出字符串的地址。
%u unsigned 以十进制数输出unsigned型数据(无符号数)。注意:%d与%u有无符号的数值范围,也就是极限的值,不然数值打印出来会有误。
%c char 用来输出一个字符,
%s string 用来输出一个字符串,
%f float 用来输出实数,以小数形式输出,默认情况下保留小数点6位。
%11.9f float 用来输出实数,总位数11位,小数点后9位
%e 以指数形式输出实数,
%g 根据大小自动选f格式或e格式,且不输出无意义的零。
变量
1. 数据类型
基本类型
int
char
float
double
_Bool(布尔)
enum(枚举)
指针类型
构造类型
空类型
2. C的数据类型有点特殊,他的大小会根据不同的环境会有所异同,这就很尴尬了.我只能大概知道他的大小.而且还可以分为有符号signed和无符号unsigned
char 1byte;
short 2byte;
int 4byte;
long 8byte;
emmm,和java其实一样呀
3. 进制转换,略
4. 反码,补码,略
举个栗子:
[solofeng@com C]$ vi helloworld.c
==========================
#include <stdio.h>
#define name "guangda"
int main(){
printf("hello world! \n");
printf("hello ,i m %s.\n",name);
return 0;
}
==========================
[solofeng@com C]$ rm helloworld
[solofeng@com C]$ gcc helloworld.c -o helloworld
[solofeng@com C]$ ls
helloworld helloworld.c
[solofeng@com C]$ ./helloworld
hello world!
hello ,i m guangda.
上文中的#define name "guangda"
注意#在C中不是注释,放在前面表示宏定义(大概可以理解为预定义,在这里就是预定义了一个全局变量name,所有函数皆可用)
疑问: int,char,float等类型的字节数是固定的,string实际上是字符数组,那么C如何让cpu知道哪里是数组的结尾
回答: 实际上,存储字符串数组时,除了字符串本身以外,在最后一个字符的后一位存储一个"\0"表示数组的结束,当cpu读取到"\0"时,便明白字符串结束了,不再往下读取
运算符&语句&注释
+ - * / %
++ -- += -= *= /= %=
< > = != >= <= ==
>> <<
& | ~
&& || !
sizeof (长度运算符)
. (对象运算符)
-> (指针运算符)
pow函数->幂函数(例如求2的10次方)
if(){}
if(){}else{}
if(){}else if{}else{}
switch(){
case num: expression; break;
default: expression; break;
}
while(){}
do{}while();
for(){}
三元exp1 ? exp2 : exp3;
//
/**/
数组
int list[10] = {1,2,3};
sizeof(list)结果是返回数组占的空间的大小,而不是元素的个数
char name[] = "guang";
char name[] = {'g','u','a','n','g'};
第一步进阶
重点:指针
关于指针变量和普通变量的区别:
普通变量有类型(int/long/...),编译器知道该变量对应的内存地址,内存中存放着对应的值;
指针变量也有类型,就是指针变量类型,大小为4byte,编译器知道该指针变量对应的内存地址,然后该内存地址中存放指针的地址;
同时,定义指针变量时的类型为具体的值得类型.比如,指针的地址存放着值为字符,则定义:
char *pa
格式:
类型名 *指针变量名 (定义时需要加上*来表明他是一个指针变量,但是他真正的变量名并不包括*)
char *pa
int *pb
取址运算符&
举栗子:初始化指针变量
char *pa = &a;(获取a变量的内存地址赋值给指针变量*pa)
取值运算符*
printf("%c\n",*pa);(获取指针变量*pa的值并输出)
不好意思,我TM晕指针了;等我搞明白了,再回头过来补
函数
函数声明+函数定义;声明要在定义之前
> 测试一下值传递
1 #include <stdio.h>
2
3 void swap(int x,int y);
4 void swap(int x,int y){
5 int temp;
6 printf("in swap function,互换前:x=%d,y=%d\n",x,y);
7 temp = x;
8 x = y;
9 y = temp;
10 printf("in swap function,互换后:x=%d,y=%d\n",x,y);
11 }
12
13 int main(){
14 int x = 3, y = 5;
15 printf("in main function,互换前:x=%d,y=%d\n",x,y);
16 swap(x,y);
17 printf("in main function,互换前:x=%d,y=%d\n",x,y);
18 }
19
20 /*
21 *
22 * 以下为输出结果:
23 in main function,互换前:x=3,y=5
24 in swap function,互换前:x=3,y=5
25 in swap function,互换后:x=5,y=3
26 in main function,互换前:x=3,y=5
27 *
28 *
29 * 当初学java的时候,有点小迷惑:这是为什么呢
30 * 后来想到了一招,把main中的x,y换成了i,j.emmmm,瞬间理解了
31 * 就是说main中的xy和swap中的xy就不是同一个东西.
32 *
33 * 后面,对方法的作用域理解得深一点了,恍然大悟,emmm,的确不是同一个东西
34 *
35 * 再后来,小明老师提出了一个问题:java有没有引用传递.
36 * 我当时说:没有,java只有值传递,传递对象的引用时也不过是传递了对象的地址,这个地址就是"值".
37 * 好像离题了,上面两行当我没说
38 *
39 * emmm,类比到C.这里的方法调用,作用域等,很明显跟java是同出一辙的
40 * 但是,C之所以叫C,是因为他还有个流弊的东西,叫指针,使用指针就可以实现非值传递-->地址传递
41 * 以上面的两个函数举栗子,具体点来说,就是在swap中修改main中的变量
42 * 哇,有点作用域穿透了的感觉
43 * 瞬间觉得C高大上啊
44 * 具体实现请参考swap_num_sup.c
45 * */
46
47
> 测试一下地址传递
1 #include <stdio.h>
2
3 void swap(int *px,int *py);
4 void swap(int *px,int *py){
5 int temp;
6 printf("in swap function,互换前:x=%d,y=%d\n",*px,*py);
7 temp = *px;
8 *px = *py;
9 *py = temp;
10 printf("in swap function,互换后:x=%d,y=%d\n",*px,*py);
11 }
12
13 int main(){
14 int x = 3, y = 5;
15 printf("in main function,互换前:x=%d,y=%d\n",x,y);
16 swap(&x,&y);
17 printf("in main function,互换前:x=%d,y=%d\n",x,y);
18 }
19
20 /*
21 *以下为输出结果
22 in main function,互换前:x=3,y=5
23 in swap function,互换前:x=3,y=5
24 in swap function,互换后:x=5,y=3
25 in main function,互换前:x=5,y=3
26 *
27 * 原理就不解释了,因为无论是main,还是swap,都是在操作&x,&y地址的值.
28 *
29 * */
测试返回字符串
#include <stdio.h>
2
3 char *select(char simplename);
4 char *select(char simplename){
5 switch(simplename){
6 case 'A':
7 return "Apple";
8 case 'B':
9 return "Banana";
10 case 'C':
11 return "Cat";
12 case 'D':
13 return "Dog";
14 default:
15 return "None,please input from ABCD";
16 }
17 }
18
19
20 int main(){
21 char input;
22 printf("请输入一个选项(ABCD)");
23 scanf("%c",&input);
24 printf("%s\n",select(input));
25 return 0;
26 }
27
28 /*
29 * 有一个问题,关于返回值的
30 *
31 * 复杂的比如结构体,先不说,字符串怎么返回?
32 * 因为C中就没有关于字符串的定义啊,只有字符数组
33 * emm,这个时候,指针函数就能帮到你
34 * 一牵扯到指针,自然而然就联想到内存地址,没错,指针函数返回的就是地址
35 * 那上面的代码举栗子:就是返回字符串的第一个字符的地址.聪明的gcc会自动往下读取直到\0,从而获取完整字符串
36 * */
函数指针和指针函数
从名字就能看出来,函数指针是指针;指针函数是函数,其实没有什么好混淆的
不过,这个函数指针有个什么鸟用呢
如果想调用函数,直接调用接好了,传个屁啊,多此一举
实践出真知,下面用代码来一探究竟咯
没有使用函数指针:
1 #include <stdio.h>
2 int add(int i,int j);
3 int sub(int i,int j);
4 int add(int i,int j){
5 return i+j;
6 }
7 int sub(int i,int j){
8 return i-j;
9 }
10 int main(){
11 int i=10;
12 int j=3;
13 printf("10+3=%d\n",add(i,j));
14 printf("10-3=%d\n",sub(i,j));
15 }
使用了函数指针:
1 #include <stdio.h>
2 int add(int i,int j);
3 int sub(int i,int j);
4 int calc(int (*pfunction)(int x,int y),int i,int j);
5 int add(int i,int j){
6 return i+j;
7 }
8 int sub(int i,int j){
9 return i-j;
10 }
11 int calc(int (*pfunction)(int x,int y),int i,int j){
12 return (*pfunction)(i,j);
13 }
14 int main(){
15 int i=10;
16 int j=3;
17 printf("10+3=%d\n",add(i,j));
18 printf("10-3=%d\n",sub(i,j));
19
20 printf("10+3=%d\n",calc(add,i,j));
21 printf("10-3=%d\n",calc(sub,i,j));
22
23 }
24
25
26 /*
27 * 对比一下,使用了函数指针和没有使用函数指针的区别,emmm
28 * 好像还真是多此一举啊
29 * 使用了函数指针的功能,我不用也能实现,还更简洁,(╯‵□′)╯︵┻━┻
30 *
31 * 仔细想想的话,这东西好像跟java的泛型有点说不清的联系
32 * 对外暴露一个统一的大范围的接口
33 * 然后具体实现时,调用具体的接口
34 *
35 * 感觉真没有什么卵用
36 * 不管了,使用到了再说
37 * */
38
39
链接属性
external(默认): 整个项目内有效 extern
internal: 只在本文件内有效 static
none: 无效的属性
所谓的链接属性是相对文件作用域内而言的;拥有链接属性的有比如:函数名,全局变量.
所以上面的external,也就是说可以a文件中调用b文件的全局属性或者方法;
需要小心的是不要忘了定义链接属性或者重复定义链接属性
举个栗子:
[solofeng@com utils]$ vi aad_external_internal.c
=========================
1 #include <stdio.h>
2 int count = 12;
=========================
[solofeng@com utils]$ vi aad_external_internal_sup.c
=========================
1 #include <stdio.h>
2 extern int count;
3 int main(){
4 printf("external count=%d\n",count);
5 }
=========================
[solofeng@com utils]$ gcc aad_external_internal.c aad_external_internal_sup.c -o aad_external_internal
[solofeng@com utils]$ ./aad_external_internal
external count=12
总结一下,这几个东西就是java里面的public/protected/default/private
生命周期&存储类型
生命周期:
1. 静态存储期(全局变量,函数)
2. 动态存储期(局部变量)
存储类型:
auto
register
static
extern
typedef
auto:局部变量默认类型
int age = 10;
auto int age = 10;
上面两个是等价的
auto这个属性需要声明在代码块作用域中
register:寄存器变量
可能会被放到寄存器的变量 ,具体放不放那要看编译器的
register这个属性需要声明在代码块作用域中;且不能使用取址运算符&,因为有可能去了寄存器嘛,而寄存器地址不允许获取
static:
修饰全局变量:那么变量链接属性修改为internal,变量的作用域为本文件.
修饰局部变量:变成静态的局部变量,特点是拥有静态存储期,就是说,出了原本的作用域,他占的内存也不释放,与整个项目程序同寿.(跟java中的静态变量类似)
疑问:但是在作用域以外可以访问他吗,如果不能,那他有个屌用?
==============================
1 #include <stdio.h>
2
3 int main(){
4 int i;
5 for(i=0; i<10; i++){
6 static int result = 0;
7 result += i;
8 printf("this %d trun ,the result is %d\n",i,result);
9 }
10 //printf("at last,the result is %d\n",result);
11
12 }
==============================
以上是反面例子,的确不能在作用域以外访问,如果一定要问有什么用,暂时想到一个计数器(先不管同不同步什么的)
==============================
1 #include <stdio.h>
2
3 //计数器方法,每次调用时count自增一次.
4 int counter(){
5 static int count = 0;
6 count++;
7 }
8
9 int main(){
10
11 printf("假设程序段A调用了一次支付操作\n");
12 printf("一共使用了多少次支付操作:%d\n",counter());
13
14 printf("假设程序段B调用了一次支付操作\n");
15 printf("一共使用了多少次支付操作:%d\n",counter());
16
17 printf("假设程序段C调用了一次支付操作\n");
18 printf("一共使用了多少次支付操作:%d\n",counter());
19 }
==============================
[solofeng@com utils]$ ./aae_static_variable_counter
假设程序段A调用了一次支付操作
一共使用了多少次支付操作:1
假设程序段B调用了一次支付操作
一共使用了多少次支付操作:2
假设程序段C调用了一次支付操作
一共使用了多少次支付操作:3
那我就更有问题了,既然如此,我直接拿一个external全局变量去操作不就ok了.
但是external全局变量可视范围太广了,不安全.一不小心就被别人篡改了数据.
extern: 略
typedef: 略