C语言学习之路

C语言学习之路-由浅入深(快速掌握c基础)

 

注明:本博客只适用于有java基础的人观看,因为java是c的升级版,所以下面我们会用java来与c比较

1.第一个C程序:HelloWorld.c

首先我这里是使用这个软件编写的:下载地址

安装过程一直next就好了

安装后在你的代码目录创建一个HelloWorld.c,代码目录可以随意,然后双击打开HelloWorld.c就可以默认进入我们下载的c开发软件中,如图:
这里写图片描述

其中图中标记为我们java中常用的编译和运行

下面就可以开始我们c语言的第一个helloWorld,通常说从helloWorld开始可以快速成为大神:

#include <stdio.h>    // java  import xxx.xx.pack    引用函数的声明  
#include <stdlib.h>
 main() // 程序的入口函数  
{
      //代码文件目录是本级目录则会执行成功,否则会找不到该类
      printf("Hello world !\n"); // 控制台打印一个hello world 

      //如果不是本级目录必须先指定目录地址,才能执行成功
      system("java -classpath c:\\ HelloWorld");

      system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

代码中我们首先可以看到
1、这里写图片描述
这个东西拿java来说就是我们通常的包名

2、main函数是主入口,和java一样

3、c语言中必须使用system(“pause”);使命令行暂停,方便观察程序的执行结果,否则结果会在你眼中一闪而过


2.C语言的基本类型与JAVA基本类型对比:

上一节中我们开始我们的第一个helloWorld,下面我们讲学习c中的数据类型:

// java数据类型 和长度int  4个字节 double  8个字节  float 4个字节 long 8个字节 
// short 2个字节 boolean  1个字节 char 2个字节 byte   1个字节 
// char, int, float, double, signed, unsigned, long, short and void
// c语言中 数据类型比java少一些 在c语言中没有 boolean类型的数据  int 1 代表真 0 代表假
// c 语言中没有String类型的数据   java中表示一个字符串 String  , c语言中表示字符串 通过char类型的数组来表示字符串
// c 语言没有byte类型   所有用char的类型表示byte类型 

#include <stdio.h>    
#include <stdlib.h>
// sizeof(); c语言的一个函数 可以把 某种数据类型的长度获取出来 int 
 main() 
{          // %d 类似sql语句的? 占位符  
            printf("char的长度为%d\n", sizeof(char));//1
            printf("int的长度为%d\n", sizeof(int));//4
            printf("float的长度为%d\n", sizeof(float));//4
            printf("double的长度为%d\n", sizeof(double));//8
            printf("long的长度为%d\n", sizeof(long));//在不同的情况下可能会有不同的大小,但是long的长度一定比int大 4
            printf("short的长度为%d\n", sizeof(short));// 2

            //signed, unsigned, 数据类型的修饰符
           // signed int ; 代表的是有符号的int的数据 
           // unsigned int ;  无符号的int数据 
              printf("signed int的长度为%d\n", sizeof( signed int));//4
              printf("unsigned int的长度为%d\n", sizeof( unsigned int));//4

           // 符号的修饰符 只能修饰 整数类型的数据 long int 
           // 不能修饰 浮点型的数据 float double  
           // printf("signed float的长度为%d\n", sizeof( signed float));

              system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

从上面我们可以知道,c语言有以下几种数据类型:
char, int, float, double, long, short
使用char表示java的byte类型数据
使用char数据去表示java中String类型的数据

c的两种修饰符
signed, unsigned,


3.C语言中的输入输出函数:

上一节我们了解c语言的基本数据类型,下面我们看看c的输入输出函数:

/*%d  -  int
%ld – long int
%c  - char
%f -  float
%lf – double
%x – 十六进制输出 int 或者long int 或者short int
%o -  八进制输出
%s – 字符串

Int len;
Scanf(“%d”,&len);*/
#include <stdio.h>    // java  import xxx.xx.pack    引用函数的声明  
#include <stdlib.h>
 main() // 程序的入口函数  
{    int i = 3;
     float f = 3.1415;
     double d = 6.2815;
     char c = 'A';   //通过单引号定义字符 
     short s = 2;   

     //输出的时候占位符和数据类型必须一一对应,否则得不到正确的结果
     printf("int i=%d\n",i);
     printf("float f=%f\n",f);
     printf("char c=%c\n",c);
     printf("double d=%lf\n",d);
     printf("short s=%d\n",s);

     /*char arr[20] ; //定义一个长度为20的数组
     //java中是System.in
     scanf("%s",arr);  // 从键盘接受一个字符串,放在c数组里面 

     //java中是System.out
     printf("s =%s\n",arr); 
       */

     int j ;
     scanf("%d", &j);//&代表的是取地址
     //从控制台得到j地址所代表的址输出  
     printf("j=%d\n",j); 

     system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

从代码中我们知道c语言的
输入:scanf();函数 根据地址去输入&j
输出:printf();函数


4.指针入门:

上一节我们学到了输入输出,下面我们将学习一个新名词指针
首先java中是没有指针这个名词的,

指针是什么? 指针就是一个地址
地址代表的就是一块内存空间
指针变量是什么? 用来存放指针

从上面我们就可以知道java中的内存控件就是c语言中的指针,下面我们看下代码:


#include <stdio.h>    
#include <stdlib.h>
 main() 
{    


      int i =5;// 定义一个int 类型的变量i 值 =5 
      //%#X表示16进制的地址占位符
        printf("i的地址 %#X\n",&i); 

      //获取i的地址,&i就是一个指针
      // &i; 
      //定义一个指针变量,数据类型*
      int* p ; // 指针变量 定义一个int* 类型的变量p  
      //其他两种表示方式:int *p, int * p;


      p = &i;  // 就是把i的指针赋给指针变量p  ,现在指针变量p里面存放的内容(数据)  就是i的地址  


      printf("p里面的内容为(i的地址) %#X\n",p); 

      //*号 操作符
      // *号的几种含义  
      //1 . *号放在某种数据类型的后面,代表就是这种数据类型的指针  int*   float* 
      //2 . *号 代表一个乘法符号  3*5  = 15;
      //3 .  *号放在一个指针变量的前面 -> 代表取这个指针变量所存放的地址里面对应的数据  
       printf("i=%d\n",i);
       printf("*p的值%d\n",*p); 

       // 改变p的值 会不会影响i的值? 
        //p = NULL;
       // printf("i=%d\n",i);//5

       // 改变i的值 会不会影响p的值? 
       // i = 100;
       //  printf("p里面的内容为(i的地址) %#X\n",p); 
       // 通过上述实验 p和 i 是两个不同的变量 ,改变i的值 不会影响 p的值,同理,更改p的值 也不会影响i的值  

       // 更改*p的值 会不会影响i的值  
      // *p  = 88;
       // printf("i=%d\n",i); //88

       // 更改i的值 会不会影响 *p的值呢?
      // i = 99;
      // printf("*p的值%d\n",*p);  //99

        //*p 和i 其实代表的是同一个变量,代表是同一块内存空间  


       system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

从代码中我们可以知道:

  • 指针就是地址,而c语言中我们在变量前面加上&符号就可以到地址也就是指针如:int i=9;那么i的指针表示方式是&i

  • 指针变量就是用来存放指针的一个变量,如:数据类型* 变量名;如:int * p 这样就是一个指针变量,将我们上面的&i=p这样我们就将i的指针放在了指针变量p中

  • 得到指针变量中的值使用符号*,例如我们将上面指针变量p的指针i的值取出来表示就是:*p

  • *p 和i 其实代表的是同一个变量,代表是同一块内存空间


5.指针介绍:

上一节我们已经知道了指针和指针变量的用法,下面我们将通过一个小程序进一步说明指针


#include <stdio.h>    
#include <stdlib.h>
 main() 
{       
        // 所有的变量都会分配一块内存空间

        // 指针就是用来表示一块内存空间的地址的  

        // 地址可以用过 &这个符号获取到某个变量的在内存中的地址 

        // 这个地址如果想把他存放起来 就需要有一个变量 去存放这个地址

        //  存放内存地址的变量 就是指针变量  


        // 指针和指针变量 
        // 指针是用来表示一块内存地址的,
        // 指针变量是用来存放一个内存地址的 .

        //  
        printf("ready go! 剩余时间60秒\n");

         int time = 60;
         printf("time变量对应的内存地址为%#X\n", &time); 

          for(;time>0;time--){



                 printf("剩余时间%d\n",time); 


                 sleep(4000);     
      }                 
       printf("游戏结束");

        system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40

这里写图片描述

从代码中我们编写了一个for循环,打印time指针值,可以在命令行程序已经在开始走了

这里写图片描述

从图中可能大概你们可以看到我使用一个外挂软件找到了time指针值,外挂地址

怎么使用外挂软件:
这里写图片描述

  • 首先点击图中的箭头图标,
  • 弹出Process List在里面找到我们的程序,然后点击,
  • 左边Address栏就会出现我们程序中的time地址

这里写图片描述

从图中我们居然看到我们的程序运行到22的时候怎么又从59开始了,正常情况下我们是21啊,这是怎么回事呢?
哈哈,这其实就是用到了我们上图 中的外挂,首先我们使用外挂找到我们的time地址,然后将time 的值改为60之后就发现命令行又从60开始了。

通过这个外挂我们就更加深刻了解指针的作用,下面我们将使用几个案例去了解指针的一些细节


6.案例:使用指针交换两个数据:

#include <stdio.h>    
#include <stdlib.h>
// 问 java 中有值传递和引用传递 吗? 他们的区别是什么? 

// 其实在java中只有值传递 , 没有引用传递  

// Person p = new Person();  p里面存放的内容 就是person对象的地址 




void swap2(int* p , int* q){ // 传递的形参为 i 和j 变量的地址  
     // *p  代表 i   *q 代表就是 j
     int temp;
     temp = *p;
     *p = *q; 
     *q = temp;
} 


void swap1(int i ,int j){ //  形参 i 和j 跟主函数里面的i和j是两个不同的变量 
           printf("子函数 i 地址%#X\n",&i);
           printf("子函数 j 地址%#X\n",&j);
           int temp;
           temp  = i;
           i = j;
           j = temp;

}
 main() 
{   
           //利用指针 可以在子函数里面修改主函数里面的数据  
           int i = 3;
           int j = 5; 
           printf("i=%d\n",i);
           printf("j=%d\n",j);
           printf("主函数 i 地址%#X\n",&i);
           printf("主函数 j 地址%#X\n",&j);


           /*/交换两个数字
           int temp;
           temp  = i;
           i = j;
           j = temp;
           */
           //方法一中确实将i,j的值交换了,但是当函数一执行完,函数被回收,i,j就被销毁了,交换失败
           // swap1(i,j);

           //方法二接收的是i,j的地址,i,j在main函数里,所以数据交换成功
           swap2(&i,&j); 
           printf("交换后\n"); 
           printf("i=%d\n",i);
           printf("j=%d\n",j);


            system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58

7.案例:使用指针获取子函数的数据:

#include <stdio.h>    
#include <stdlib.h>

//int** q  表示里面存放的是一个int* q的指针
f(int** q){
    int i = 3; 
    printf("子函数 i的地址 %#X\n",&i); 
    // *q   代表的就是p变量  
    *q = &i; 
} 
/**
   使用指针的时候 不可以访问已经被系统回收掉的数据  
   子函数执行完毕后 子函数里面所有的局部变量都会别系统回收  
*/

 main() 
{  
        // 希望在主函数里面去使用子函数里面的变量 i 

        // f(); 

        // 希望在主函数里面得到子函数 里面int i变量的地址 

        int* p ; //存放子函数f中 int i的地址的一个变量


        f(&p); 

         /**在6中我们已经说过函数会被回收,
          * 当f()函数一执行完,就会被回收,i就会被回收
          * 但是回收是有一定的时间,所以如果我们在正在回收还未回收完全时去取值,
          * 会得到正确的值或者地址,但是如果我们比如下面我先打印地址值,在去打印*p
          * 此时p是i的地址值,而*p就不是了,因为此时i被回收了,但是我们注释输入地址值
          * 此时*p就会成功打印i的值
          */
       // printf("主函数 i的地址 %#X\n",p); 

       // printf("i的值为 %d\n",*p); 

       system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果            
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

8.案例:使用指针返回一个以上的值

#include <stdio.h>    
#include <stdlib.h>
 // public List<Person> getPersons() {}; 
 // public byte[] getbytes(){};

 /*
 如果让子函数 更改主函数里面的数据  
 如何让子函数 返回一个以上的值 
 1.子函数的形参 为 主函数中要修改的变量的地址 
 2. 调用子函数的时候 把要修改的变量的地址 传递给子函数 
 3. 在子函数里面 修改这个地址里面存放的变量的内容 
 4. 主函数使用这个变量的时候 里面的值就发生了变化  

 */

 int f(int* p, int* q){
    *p =  33;
    *q = 55;

 }


 main() 
{   

    int i = 3;
    int j = 5;
    f(&i,&j);
    printf("i=%d\n",i);//33
    printf("j=%d\n",j);//55




              system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

6.7.8案例总结:
1.子函数在main函数中调用结束后会被销毁,随之传入的形参也会被销毁
2.main函数里的数据要想通过子函数进行交互,子函数的传入的参数必定是指针变量
3.方法中的返回值不能像java中一样返回集合之类的数据,通过指针去返回多个数据。


9.指针的常见错误:

通过6、7、8节我们细致的了解了指针,下面我们将讲解指针的一些错误:

#include <stdio.h>    
#include <stdlib.h>
 main() 
{   
      /*    int* p; //定义一个指针变量   垃圾值 -> 野指针  

           //printf("*p=%d\n",*p); 
          *p = 1231; 
          // 立刻蓝屏
          // 
          指针变量如果没有赋值就不能使用  
          */ 
         /* int d = 324233;
         char* c; ;  // 编译错误  不符合的指针类型  

         c = &d;
         printf("*p = %c\n",*c); 
         类型不相同的指针不可以互相转换 
         */
          system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

错误总结:
1.指针变量中必须得有指针,否则在取指针变量中的指针的值的时候会异常
2.不能给没有定义指针的指针变量的指针的值赋值
3.指针变量和指针的数据类型必须一一对应


10、指针占多少个字节:

之前第二2节中我们知道了c的基本数据类型的字节数,那么指针占多少个字节呢?
看代码:

#include <stdio.h>    
#include <stdlib.h>
 main() 
{  
        int i =3;
        double d = 3.141692;
        float f = 3.1423;
        char c ='B';

        int*  ip = &i;
        double* dp = &d;
        float* fp = &f;
        char* cp = &c;

        //电脑不同输入的值可能是4可能是8
        printf("int 类型指针变量的长度为 %d\n",sizeof(ip));  //8
        printf("double 类型指针变量的长度为 %d\n",sizeof(dp)); //8
        printf("float 类型指针变量的长度为 %d\n",sizeof(fp));     //8   
        printf("char 类型指针变量的长度为 %d\n",sizeof(cp));   //8     



        // 在32位的操作系统上 因为程序 最大能使用的内存空间的地址 就是2的32次方
        // 指针只需要4位 就可以表示出来所有的内存空间 

        // 64 并且编译支持64位 8位 

         system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

首先我们定义了不同的几个变量,然后将他们的地址放入指针变量中,最后输出,我们居然发现输出结果都一样。

总结:

  • 指针变量的内存大小是固定的,与数据类型没有关系
  • 指针变量的内存大小最终取决于我们的电脑

11、使用char* 指针表示字符串

在第二节中我们知道,c语言中是使用char数组去表示一个java中的String字符串,
在我们学习指针之后我们将看看怎么用指针更加简单的表示一个字符串:

#include <stdio.h>    
#include <stdlib.h>
 main() 
{  
    char arr[20] ={'h','e','l','l','o','\0'};    

    // 利用char类型指针 方便的表示一个字符串  
    char* arr1= "hello world";

    printf("%s",arr1);    

    system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

从代码中我们可以看到使用char数组的方式是比较复杂的,然后我们使用指针的方式:char* arr1=”“,这样就可以简单的直接表示一个字符串

注意:在java中我们定义数组可以int arr[]; int [] arr;但是在c语言中[]只能卸载变量名后面,如:int arr[]


12、指针与数组

指针进阶:

#include <stdio.h>    
#include <stdlib.h>
// 数组是一块连续的内存空间  数组名 就是内存空间的首地址 
// 数组名[i]  ==  *(数组名+i); 
 main() 
{  
            /* char[] arr = new char[20]; 
             char arr[] ; 
             */
             // 创建一个长度为5的int类型的数组  
            int arr[5] ={1,2,3,4,5};

            printf("a[0]=%d\n",arr[0]);


            printf("a[4]=%d\n",arr[4]);

             // 逻辑上是错误的代码  数组下标越界  
            // printf("a[5]=%d\n",arr[5]);
            // windows xp   缓冲区越界补丁  


            // arr是一个什么东西呢?
            printf("arr = %#X\n",arr); 

            // 打印 数组的第一个元素的地址 
            printf("arr[0]地址 = %#X\n",&arr[0]); 

            // 打印数组中的第二个元素 
            printf("arr[1]=%d\n",arr[1]); 
            printf("arr[1]=%d\n", *(arr+1));
            //问题: arr[i]  *(arr+i)  代表的是同一个变量么? 
            // 代表的是同一块内存空间 指向的是同一个变量  


            //通过实验 : 数组名表示的 就是这个数组第一个元素 的首地址 

               system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42

总结:

  • 语言语法上没有数组越界,在逻辑上存在数组越界,
  • arr是一个指针,arr的指针就是&arr[0]的指针,也就是说数组的指针就是数组第一个元素的指针
  • 数组中的数据的指针是一块连续的内存空间
  • arr[i]等同于*(arr+i)

13、指针的计算

通过上一节我们大概知道了指针与数组的关系,下面我们通过一个案例去巩固一下:

#include <stdio.h>    
#include <stdlib.h>
 main() 
{  
        int i =3;  //天津的某个路上 盖了一个房子 3 
        int j =5;  // 北京的某个路上 盖了一个房子 5 
        int* p = &i; // p 天津的门牌号 
        int* q = &j; // q 北京的门牌号 


        // 指针的运算和数组都是紧密关联的 
        char arr[5]={'a','b','c','d','e'};  //一块连续的内存空间 

        char* p1 = &arr[2];
        printf("char = %c\n", *(p1-1));

        // char 内存中占用 1个字节 
        // int 内存 中占用 4个字节  

         int intarr[5]={1,2,3,4,5};  //一块连续的内存空间 

        int* q1 = &intarr[2];
        printf("char = %d\n", *(q1-1));



        // 指针的运算 按照 约定好的数据类型   偏移相对应的内存空间的大小  

        system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

总结:

  • 数组中的数据的指针是一块连续的内存空间
  • 指针的运算按照约定好的数据类型偏移相对应的内存空间的大小

14.案例:通过子函数打印数组

#include <stdio.h>    
#include <stdlib.h>
#define pi 3.1415 
// 写一个子函数 打印数组里面的每一个元素 

void printArr(int* arr, int len){ // arr是数组的首地址  len数组的长度  
     int i=0;
     for(;i<len;i++){ // 在c99 的语法格式下  for循环的初始化条件 不能写在 for 循环的括号里面  
        // printf("arr[%d]=%d\n",i,arr[i]);  // arr[i] 和  *(arr+i) 代表的含义相同 
        printf("arr[%d]=%d\n",i, *(arr+i));
     }
} 


main() 
{  
       // int arr[10]={1,2,3,4,5};

         printArr(&arr[0],10);
        //1 .定义一个数组  缺陷 数组的长度 必须事先申请好 
        //2. 循环赋值
        //3. 打印数组里面的内容 

        system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果          
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

总结:

  • int arr[5]; 这一句代码一旦执行 ,就立刻会在内存里面申请 5个内存空间 每个内存空间的大小可以存放一个int类型的数据
  • 留下疑问:没有办法动态的增加这一块空间的大小, 也没办法减小这一块内存空间,只能提前写死,例如我们代码中arr[10],直接就将我们的arr数组大小写死了

15、realloc()方法介绍

在上一节中我们留下的疑问是不能更改数组的内存控件,而realloc()方法则完美解决了这个问题,具体使用方式:

int arr[10];
arr =  realloc(arr,sizeof(int)*12); //空间的长度为12了 
  • 1
  • 2

首先我们可以看到realloc接收两个参数:

  • 第一个参数表示你需要更改大小的变量
  • 第二个表示更改之后的长度

我们了解了realloc()方法是可以更改变量内存大小的,那么就会存在这样一个问题,改变之后的内存会不会覆盖之前的数据呢?

  • 对于增加内存:realloc是直接在原有的基础上添加内存空间,所以还是会保持原有的数据再进行添加

  • 对于减少内存:realloc是会将排列在最后的内存空间值给回收掉的,
    比如原始数据int arr[4]={1,2,3,4};减少2个内存的话,就是int arr[8]={1,2}


16、动态分配内存

前面我们看到我们定义的那些函数变量等,都是由系统给我们自动分配内存,我们是不用去理会系统到底什么时候去给我们分配,什么时候去回收,

而接下我们要学习的是自己去管理内存即动态分配内存,看代码:

#include <stdio.h>    
#include <stdlib.h>
#include <malloc.h> 
// malloc memory allocate 内存申请 
 main() 
{  
   // 接受一个参数 申请多大(byte)的内存空间  
  int* p = (int*)malloc(sizeof(int)); // 在堆内存里面申请一块可以存放一个int类型数据的内存空间  

  *p = 4;  // 就是往 p 里面存放的地址 表示的那块内存空间里面存放一个int类型的数据 4 



  printf("*p=%d\n",*p);  


system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

代码中我们首先如java导入类的方式,在代码上面添加了一行:这里写图片描述
这个就是动态内存所要用到的方法了,接下来我们在main函数中
int* p = (int*)malloc(sizeof(int));
这样我们即给指针变量p分配了int字节大小的内存空间,完成了一个动态分配内存的操作

这样我们就可以使用如上方式去解决上一节中的疑问了

学到的知识点:

  • malloc(byte length)方法用于动态申请内存。
  • 动态申请内存其实就是在申请指针

疑问:

  • 我们之前看到的,函数使用完成后是会被回收掉的,难道我们动态申请内存怎么被回收呢?不然是会浪费内存空间的

17、动态分配内存2

在上节中我们已经知道了malloc(byte length)方法是用来动态分配内存的,
下面我们将通过一个小案例来进一步讲解动态分配内存,并且解决上一节中的疑问

#include <stdio.h>    
#include <stdlib.h>
#include <malloc.h>
f(int** address){ //address 存放的是q的地址 
 // 动态的在堆内存里面申请一块空间 



   int* p ;
    p = (int*)malloc(sizeof(int)*3); 
   *p = 3;
   *(p+1) = 4;
   *(p+2) = 5 ; 
   printf("子函数里面 地址%#X\n",p); 
   *address  = p; 

   // 在子函数里面把p释放掉了  
   //free(p);
} 


 main() 
{  

       int* q ; 

       f(&q); 


       printf("主函数里面 地址%#X\n",q); 
       printf("*q = %d\n",*(q+0)); 
       printf("*q = %d\n",*(q+1)); // 残留的内存映像  
       printf("*q = %d\n",*(q+2)); 


       //动态内存分配 程序员可以自己手工的决定一个变量的生命周期  

       //手工的释放调用内存空间  

       //不要使用已经回收掉的内存空间里面的数据  

        system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

首先我们看到是f(int** address) 其中int **表示的是多级指针,下面我们会详细介绍,在这里只要知道他是一个存放指针变量的指针变量就行了,
然后定义一个指针变量p并动态分配内存和赋值,
最后将p传递给f(int** address)的形参

接着我们看到main函数中我们使用了f()函数并得到了f()函数中的值

细心的同学可能可以看到我们在f()方法中可以看到我们注释的一个函数:free(p)

free(p):将动态释放的内存给回收,使用free(p)函数即可解决我们在上一节留下的疑问,其中p是我们需要释放内存的指针变量

接着我将free(p)打开,按理说p分配的动态内存空间的数据是会被free(p)执行后给回收的,最后居然发现在main函数打印(q+0)为0,(q+1)为4,(q+2)为5,
结果可想而知free(p)难道只将第一个数据给回收了?其实在执行free(p)之后确实将p给回收了,(q+1)为4,(q+2)为5,只是残留的映像,在回收过程中只是将p的内存空间标记为可以再次写入,其实真正并没有彻底回收,所以会出现这种情况


18、多级指针

#include <stdio.h>    
#include <stdlib.h>

 main() 
{ 

    int i = 5; 
    int*  p = &i;  
    int** q = &p;
    int*** r = &q;


  printf("i=%d\n",***r); 



 system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

一个*表示一级指针,二个*表示二级指针,以此类推


19、函数的指针

#include <stdio.h>    
#include <stdlib.h>

int add(int x , int y){

    return x+y;

}

 main() 
{ 

       int (*pf) (int x, int y); //定义一个函数的指针的声明 名字叫pf  返回值 int 接受参数两个int  
       pf = add; 

       printf("result=%d\n", pf(3,6)); 

       system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果  


}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

代码中我们定义了一个返回值int的add函数,在main函数中使用函数指针给add函数赋值,输出

函数指针使用步骤:
1.定义int (pf)(int x, int y):返回值 (*函数指针别名)(参数列表)
2.赋值 pf = add;
3.引用 pf(3,5);


20、结构体


结构体是c中的新名词,和java中的类是一样的

#include <stdio.h>    
#include <stdlib.h>

struct Student
{
    int age; //4
    float score; // 4/
    long id;  //4
    char sex ; //2   vc 6.0 14
};
 main() 
{ 
    struct Student st={80,55.6f,100010 ,'F'};   
    printf("age = %d\n",st.age);    
    printf("score = %f\n",st.score);     
    printf("id = %ld\n",st.id);    
    printf("sex = %c\n",st.sex); 
    // 结构体的长度 
    printf("长度 = %d\n",sizeof(st)); 

    struct Student* pst;
    pst = &st;



    //printf("age = %d\n", (*pst).age);   
    printf("age = %d\n", pst->age); 

    system("pause");    // 调用windows下系统的命令 让程序暂停执行  方便观察程序的执行结果      
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

从上面的代码我们可以看出首先定义除了一个Student的结构体,结构体用struct 修饰
Student定义了四个变量,

  • 给变量赋值:在main中通过 struct Student st={80,55.6f,100010 ,’F’}; 的形式给变量赋值

  • 打印变量1:如同java里一样类名.变量名,结构体名.变量名,如:我们这里是st.age…..

  • 打印变量2:首先定义一个Student类型的结构体指针变量pst,将结构体对象的指针&st放在该指针变量pst中,最后使用(*pst).变量名的方式打印,如:(*pst).age,
  • 也可以使用->符号来输出,例如我们上面的:pst->age,表示pst所指向的结构体变量中的age这个成员,在计算机内部会被转换为 (*pst).age

注意:结构体的长度是根据结构体中数据类型最多的字节大小*变量的个数,如果字节数一样,则按字节数大的来计算,例如:我们上面有3个变量的字节数为4,所以导致结构体的大小为16,如果我们的char类型为3个则是8,如果有两个4个字节的数据,两个2个字节的数据,则结构体的大小为16

结构体的常见几种表示方式:

//第一种
struct Student
{
  int age;
  float score;
  char sex;
}

//第二种
struct Student2
{
 int age;
 float score;
 char sex;
} st2;

//第三种 
struct
{
 int age;
 float score;
 char sex;
} st3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

21、联合体:

首先联合体使用union 修饰,如:union { long i; int k; char ii; } mix;

联合体 是定义一块相同的内存空间 存放里面的数据

联合体的作用就是用来表示一组数据类型 数据的数据类型为这一组中的某一种数据类型

这里写图片描述

#include <stdio.h> 
#include <stdlib.h>


main( )
 { 

struct date { int year, month, day; } today; 


union { long i; int k; char ii; } mix; 



printf("date:%d\n",sizeof(struct date));  //12 

printf("mix:%d\n",sizeof(mix));  
mix.ii = 'A';
printf("k=%d\n",mix.k);
system("pause");
} 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

从结果我们可以看出结构体输出的长度为4,按理说我们里面有三个数据,光long就为4,
这是因为mix.I mix .k mix.ii共用相同的地址,它会自动取最大的数据类型字节数为它的大小

代码中我们将mix.ii=’A’,输出mix.k发现也是65,所以我们可以很快得出一个结论:
联合体里面的数据内容会相互覆盖


22、枚举

在java中我们知道也是 有枚举的,用enum修饰,c语言在这一方面是完全一样的:

#include <stdio.h>
#include <stdlib.h>
enum WeekDay
{
Monday,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
};
int main(void)
{
   //int day;
  enum WeekDay day = Sunday;
  printf("%d\n",day);
  system("pause");
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

输出6
如果将Monday做以下改动:

#include <stdio.h>
#include <stdlib.h>
enum WeekDay
{
Monday=9,Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
};
int main(void)
{
   //int day;
  enum WeekDay day = Sunday;
  printf("%d\n",day);
  system("pause");
  return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

将输出15

使用很简单的,很java一样


23、typedef

typedef意义:声明自定义数据类型,配合各种原有数据类型来达到简化编程的目的的类型定义关键字。

例如:

#include <stdio.h>
#include <stdlib.h>
typedef int haha;

int main(void)
{

  haha i = 3; 
  printf("%d\n",i);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

输出3,

我们从代码中首先我们在main上方定义了一个 typedef int haha:表示用haha去代表int类型的数据,然后下面我们就可以在main函数使用haha定义变量赋值


24、预处理

 /**
         1.#开头的都是预处理指令
         2.预处理指令分为3种
         1>宏定义
         2>条件编译
         3>文件包含
         3.预处理指令在代码翻译成0和1之前执行
         4.预处理的位置是随便写的。
         5.预处理指令的作用于:从编写指令的那行开始到文件结束
         6.宏明一般大写,成员变量名一般小写。

         */



#define COUNT 4
        /**
         带参数的宏定义效率比函数高
         宏定义只是文本替换,不会帮我们去做任何运算。
         */
#define sum(a,b) ((a)+(b))
        #define pf(a) ((a)*(a))

        /**
         只要写了#if,在后面就必须加上#endif
         条件编译后面的条件必须是宏定义,因为预处理指令必须接的是预处理的定义
         */
#if (COUNT==3)
        printf("3");
#elif (COUNT==4)
        printf("4");
#else (COUNT==5)
        printf("5");
#endif

        //如果定义了COUNT
//第一种格式
#if defined (COUNT)
        printf("5");
#endif

//第二种格式
#ifdef COUNT
        printf("5");
#endif
        //如果没有定义COUNT
 //第一种格式
#if !defined (COUNT)
        printf("5");
#endif

//第二种格式
#ifndef COUNT
        printf("5");
#endif


 //取消宏定义之后就不能在使用
#undef COUNT
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

文件包含:

/**
 //宏定义也可以不赋值,为空,这个时候一般用文件名作为宏名,保证引入的唯一性。
         第一次是不包含的,如果不包含static_extern_______h
 #ifndef static_extern_______h
 #define static_extern_______h 定义一个

 #include <stdio.h>  引入头文件中的内容

 #endif  结束标志

 表示include时只会对一个文件中的内容include一次
 */
#ifndef static_extern_______h
#define static_extern_______h

#include <stdio.h>

#endif /* static_extern_______h */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

25、static与extern对函数的作用:

/**
 外部函数:定义的函数能被本文件和其他文件访问,
   1>默认情况下所有函数都是外部函数。
   2>外部函数不允许同名

 内部函数:定义的函数只能被本文件访问,其他文件不能访问
   1>允许不同文件中有同名的函数

 static对函数的作用:
 1>定义一个内部函数
 2>声明一个内部函数

 extern对函数的作用:
  1>完整的定义一个外部函数
  2>完整的声明一个外部函数
(默认声明和定义就是extern,所以可以省略)
 */

#include "static与extern对函数的作用.h"

//外部函数 默认就是extern
void test3(){
    printf("我是外部函数");
}

//内部函数
static void test2(){
    printf("我是内部函数");
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

26、extern与static对成员变量的作用:

/**
 全局变量分为2种:
   1.外部变量:定义的变量能被本文件和其他文件访问
     1>默认情况下,所有的全局变量都是外部变量
     2>不同文件中的同名外部变量,都代表着同一个变量

   2.内部变量:定义的变量只能被本文件访问,不能被其他文件访问
     1>不同文件中的同名变量互补影响

 static对变量的作用:
 定义一个内部变量

 extern对变量的作用
 声明一个外部变量
 */

#include "static与extern对变量的作用.h"
int d;//都代表同一个d
int d;
int d;
static int b;


void demo(){
    printf("%d",d);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

演示一个神奇的情况:

//声明一个成员变量
extern int a;
int main(){
   //因为之前是声明,后面赋值,所以输出的还是赋值的值,10
  NSLog(@"%d...",a);
  return 0;
}
int a=10;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

这里写图片描述


27、static对局部变量的影响:

/**
 static修饰局部变量的使用场合:
 1.如果某个函数的调用频率特别高
 2.这个函数的内部的某个变量值是固定不变的
 */

#import <Foundation/Foundation.h>
void test3(){
    int a=0;
    a++;
    printf("a的值是%d\n",a);

    /**
     static修饰局部变量:
     1>延长局部变量的生命周期,程序结束的时候,局部变量才会被销毁
     2>并没有改变局部变量的作用域
     3>所有的test函数都共享一个变量b
     */
    static int b=0;
    b++;
    printf("b的值是%d\n",b);
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test3();
        test3();
        test3();
    }
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

这里写图片描述

完结:C基础由浅入深快速学习,掌握以上基本就可以去完android中的jni了

版权声明:转载请注明出处,谢谢配合 https://blog.csdn.net/qq_33750826/article/details/53282904

文章标签: 基础 c语言

猜你喜欢

转载自blog.csdn.net/tuxedolinux/article/details/81221920