文章标题

本文介绍了C语言的基础知识,包括Hello World、格式化输出、变量、运算符、语句和注释,以及数组的概念。进一步探讨了C语言的进阶主题,如重点讲解的指针,其在函数中的应用,函数声明和定义,以及函数指针的使用。还触及了链接属性、变量的生命周期和存储类型等概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础

为甚么学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函数->幂函数(例如求210次方)



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: 略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值