C语言基础重点总结

重点

1.C语音程序从编辑到运行的过程

2.全局变量和局部变量的区别

3.变量在内存中的存储方式,变量类型的作用

4.什么是数组,一维数组的初始化,数组中元素的访问,数组元素的本质,数组在内存中存放的方式,数组的地址,数组长度的计算,二维数组的本质,二维数组初始化,二维数组的地址。

5.C语言如何保存字符串的,字符串常用的函数

6.什么是指针,指针变量,野指针,指针变量的作用,指针为什么要分类型,多级指针,指针加减法,指针的比较,指针数组,用指针去遍历数组,数组作为函数参数,值传递和地址传递

7.内存的五大区域,每个区域的作用,向堆区间申请空间的常用函数,字符串保存的两种方式的区别

8.预编译指令三种,每种的作用,#defineconst的区别


内容

 

1C语言程序运行过程

现在说的环境是GCC编译器,C语言,mac系统,Xcode IDE。

  a.创建C语言文件

  b.编译C语言源文件

编译就是使用编译器将源文件中的代码转换为二进制代码。

    换句话说就是编译就是把高级语言翻译成二进制的机器语言。

编译器在编译的时候,会先检查.c源文件中的代码是否符合C语法规     范如果符合才会生成.o目标文件.

  c.链接.o目标文件

       链接就是把库文件和.o文件生成可执行文件

目标文件中仅仅存储的是.c文件中的代码对应的二进制指令.
       1个程序如果想要交给CPU执行.光这样是不行的.
         还必须要为这个目标文件添加一些启动代码.
          添加启动代码的过程叫做 链接

  d.载入就是把载入模块加载到内存运行。

如果在IDE中编译就显示的错误就是一般就是语法错误,这种情况下直接去找源文件就可以了。如果通过了编译阶段,一般情况下就是没有包含头文件或者 没有引入需要的库

 

2. 变量相关

变量:变量就是内存中开辟的用来存储数据的那块空间.所以变量是用来存储数据的.

变量的类型:变量所代表的那块空间的类型就叫做变量的类型.

如果按作用域来区分变量 分局部变量和全局变量

不同点:

     1).声明的位置不同.局部变量声明在函数内部,而全局变量声明在函数的外面.

     2).作用域不同.

         a.局部变量.只能在当前函数的内部访问.

         b.全局变量.从定义这个全局变量的地方开始,后面的所有的函数中都可以访问这个全局变量.

            一般情况下.全局变量都是定义在最顶上的.#include下面.

     3).默认值不同.

         a. 局部变量.

           声明1个局部变量,如果没有为这个局部变量赋初始值.那么这个局部变量中有值,值是1个垃圾数.是个随机数.

         b. 全局变量.

           声明1个全局变量,如果没有为这个全局变量赋初始值,那么这个全局变量的初始值默认就是0会自动初始化为0

           如果全局变量的类型是char类型,并且我们也没有初始化.那么系统就会自动的这个char变量赋值1个 '\0'

            '\0'代表1个不可见的字符.这个字符的ASCII码就是0

     4).创建和回收的时间不同.

         a. 局部变量.

            CPU执行声明局部变量的那句话的时候,才会在内存中声明.

            当作用域结束以后 就会自动回收.

         b. 全局变量.

           程序一启动就在内存中创建全局变量.

           程序结束的时候,全局变量才会被回

5).在内存中存放的位置不同

          a. 局部变量.

             局部变量的储存在栈空间里

         b. 全局变量.

            全局变量在未初始化的时候放在BSS段,初始化之后放在数据段中。

声明变量的时候,其实这样的,找系统为你从高地址向低地址分配指定字节数的连续空间.

 

如何回收

    当变量回收的时候,其实就是告诉系统变量占用的字节不再使用了.

    可以分配给别的变量了.

    变量所占用的字节的数据是不会清空的.

     当再声明1个变量的时候.这个新变量有可能就是刚刚被回收的那个变量占用的空间.

     那么这个时候这个新变量中是有值的 值就是上次那个变量遗留下来的值 这就叫做垃圾值.

     所以,我们声明1个局部变量.最好是先为其初始化为0

     当将全局变量声明出来以后.系统会自动的将全局变量中的数据清零.

变量在内存中的存储方式.

1). 不同类型的变量在内存中占据不同的字节数.

        1个变量占用的字节一定是连续的.

        int 4

        double 8

        float 4

        char 1

    2). 在为变量分配字节空间的时候,是从高地址向低地址分配的连续空间.

        先声明的变量是在高字节. 后声明的变量是在低字节.

    3). 任何数据在内存中都是以其二进制的补码存储的.

        低位存储在低字节.高位存储在高字节.

    4). 每1个字节在内存中都有1个地址. 十六进制数.

        变量的地址:是组成这个变量的低字节的地址.

    5). 使用&取地址运算符可以直接拿到变量的地址.

    6). 使用%p打印地址类型的数据.

 

 

 3.数组相关

 

 数组的初始化

        1).最傻的方式.先声明数组,然后再使用下标逐个赋值.

              intarr[3];

            arr[0] = 10;

      2).在声明数组的同时就初始化数组的元素.

             intarr[3] = {10,20,30};

             将数组的每1个元素的值依次的写在后面的大括弧之中.

             intarr[3] = {10,20,30};

             在编译器编译的时候.是将这个代码换成了上面那种傻代码.

             intarr[3];

            arr[0] = 10;

    3). 在使用第二种方式初始化的时候,可以省略数组的长度

             intarr[] ={10,20,30,2,12,1,21,2,13,12,3,24,3,543,5,4,65,6,56,4,523,3,2,31,3,13,12,342,4,35,12};

    4).第四种初始化方式.只为数组的前面的元素赋值.

              intarr[3] = {10};

             这个时候,数组的第0个元素的值是10其他的元素的值被自动初始化为0.

             所以,如果我们要讲1个数组中的所有的元素初始化为0

     5).第五种初始化方式.指定下标的初始化.

            intarr[3] = {[1] = 10,[2] = 20};

            其他的元素的值就自动的初始化为0.

        

数组中数据的访问

        如果要取出数组中元素的值.那么就必须要确定到底要取那1个.

        通过下标来确定.下标不能越界,因为越界取出的值就是别人的值

 

数组元素的本质

        元素的本质,其实就是1个普通类型的变量.所以我们为数组的元素赋值.其实也就是为1个普通类型的变量赋值.

       数组之所以可以存储多个数据是因为数组中有多个元素.

         数据是存储在元素中.而元素就是1个普通变量.

         所以我们元素重复赋值的时候,新值干掉旧值的. 存储数据的是数组的元素,不是数组。

 

数组在内存中的存放方式

 

    int arr[3];

    1). 声明1个数组,

        在内存中从高字节向低字节申请连续的(数组的长度 *每1个元素的字节数)个字节的空间.

    2).下标为小的的元素在低字节.

    3).元素的值存储的是数据的二进制的补码.

    4).数组的元素的本质就是1个普通类型的变量.

        1个数组就是由多个普通类型的变量联合而成的.

        每1个元素就是1个普通变量.所以每1个元素也有自己的地址.

 

数组的地址.

 

    1).数组的地址是数组中最低字节的地址.

    2).数组的地址是数组中下标为0的元素的地址.

    3).数组的地址是数组中下标为0的元素的低字节的地址.

    4). 重点:

       数组名就代表数组的地址.

        C语言的数组名中存储的是数组的地址.

        所以.我们不能直接打印数组名,这样得不到数组的元素的值,因为数组名中存储的是数组的地址.

        所以我们应该使用 %p来打印数组名.

    数组的地址 ==数组名 == 数组中的低字节的地址 == 数组中下标为0的元素的地址 ==数组中下标为0的元素的低字节的地址.

 

数组的长度计算.

 

    1).数组的每一个元素的类型相同.所以数组的每一个元素占用的字节空间一样.

    2).使用sizeof运算符可以计算 数组总共占用的字节数.

        sizeof(数组名);就可以得到这个数组占用的总的字节数.

    3).得到数组占用的总的字节数以后,那么就可以用总的字节数除以每1个元素占用的字节数.

        就可以得到数组的长度.

        sizeof(数组名) /每1个元素的字节数.就可以得到数组的长度.

    4).不建议将字节数写死. 元素占用的字节数建议也是使用sizeof计算出来.因为不   同的系统不同的编译器

        相同的变量可能占用的字节不一样.

 

        sizeof(arr) / sizeof(元素类型);

 

        sizeof(arr) /sizeof(arr[0]);

 

 

 4.函数相关

函数定义和声明

      声明:返回值类型 函数名称([参数列表]);

      定义:返回值类型 函数名称([参数列表])

        {

               函数体;

           };

       参数其实就是定义在函数内部的1个局部变量.

       函数的参数的作用:可以将调用者的数据传递到函数的内部去使用.

        形参: 声明在函数名的小括弧里面的参数,叫做形式参数,简称形参.

        实参: 在调用的函数的时候 实际上为形参赋的值 这个值就叫做实际参数,简称实参.

       实参的类型如果和形参的类型不一致的时候,就会做自动类型转换.

         如果函数的参数是1个数组,在声明这个参数的时候,并不会去真正创建数组.

           而是去声明1个用来存储数组地址的1个指针变量.

           这个指针变量在内存中占据8个字节.

           所以,你通过sizeof去计算参数数组的字节的时候 得到的都是8

             在函数的内部使用sizeof计算参数数组的字节数,

             永远得到的都是8

所以,在函数内部无法使用sizeof计算参数数组的长度.

 

地址传递:通过形参数组指针操作数组 实际上操作的就是是实参数组.

    1). 当数组作为函数的参数的时候. 会丢失数组的长度.

        所以,这个时候还需要1个参数 让调用者将传入的数组的长度传进来.

    2). 当数组作为函数的参数的时候.

        在函数的内部去修改这个参数数组的元素,

        其实修改的就是实参数组的元素

3). 强调:只有数组作为函数的参数的时候,通过sizeof才算不出来长度.

 

5 数组相关

1.二维数组的本质

     二维数组的本质其实就是一个元素是一维数组的一位数组。

   元素类型 数组名[行数][列数];

    元素初始化:

   1). 全部初始化.

        a. 先声明,再使用下标来初始化.

        b. 在声明的同时,就初始化数组的每一个元素. *****

             intarr[3][4] 

             {

                    {10,20,30,40},//第0行的数据

                    {50,60,70,80},//第1行的数据

                    {90,100,110,120}//第2行的数据.

                };

        c. 上面这种方式初始化的时候.可以省略行数. ********

             intarr[][4] =

             {

                    {10,20,30,40},//第0行的数据

                    {50,60,70,80},//第1行的数据

                    {90,100,110,120}//第2行的数据.

                };

             根据大括弧的个数,编译器来自动确定行数.

        d. 直接省略里面的大括弧.编译器根据行数和列数自动判断哪些属于哪1行的.

           intarr[3][4] = {10,20,30,40,50,60,70,80,90,100,110,120};

        e. 上面的那种初始化方式 可以省略行数.编译器根据列数和数据的个数自动计算行数.

           intarr[][4] = {10,20,30,40,50,60,70,80,90,100,110,120};

    2). 部分初始化.

        a.  intarr[3][4] = {{10},{20},{30}};

            初始化指定行的数据. 那么其他的元素就会被自动初始化为0.

        b.  intarr[3][4] = {10,20,30,40,50};

            按照顺序去初始化每1个元素 其他的元素的值就会被自动初始化为0

            10 20 30 40

            50 0 0  0

            0 0  0  0

        c.  intarr[][4] = {10,20,30,40,50};

            省略行数,自动根据后面的元素的个数来计算行.

            其他的元素自动初始化为0

            10 20 30 40

            50 0 0  0

 

        d. int arr[3][4] ={[1]={10,20,30,40},[2]={50,60,70,80}};

           给指定的行初始化,其他的元素自动初始化为0

           0 0 0 0

           10 20 30 40

           50 60 70 80

 

        e. int arr[3][4] ={[0][1] = 20, [1][3] = 60};

            给指定的元素赋值.其他的元素自动初始化为0

            0 20 0 0

            0 0  060

            0 0 0 0

二维数组的地址

    二维数组的低字节的地址就是这个二维数组的地址.

    也就是第0行的地址. 也就是第0行的第0个元素的地址.

    二维数组名 就代表 这个二维数组的地址.

 

    二维数组的地址==二维数组名==二维数组的第0行的地址==二维数组的第0行的第0个元素的地址 == 二维数组的低字节的地址.

3 二维数组的长度以及行列计算

    1). 求二维数组的长度. 除以元素的个数.

        用二维数组占用的总的字节数 除以 每1个元素占用的字节数.

 

    2). 求行数

        二维数组的每1行占用的字节数是一样的.

        所以.用总的字节数 除以 每1行占用的字节数.就可以去到行数.

 

    3). 求列数.

        每1行的总字节数 除以 每1个元素的字节数.

        求长度: 行数 * 列数.

注意.

    1). 在声明二维数组的同时使用大括弧初始化数组的元素,这个时候行数可以省略.

        记住,列数不能省略.

 

2). 在声明二维数组的同时 如果不使用大括弧初始化元素 那么就必须要写行数和列数.

 

6.字符串相关

 

在C语言中,没有专门的类型去储存字符串类型的数据,C语言是通过字符数组来保存字符串数据的。 将字符串数据的每1个字符存储到字符数组中,并在后面追加1个'\0'代表字符串存储完毕.

 

2.常用函数

      printf函数

     使用printf函数输出存储在字符数组中的字符串数据.使用格式控制符%s 就可以输出存储在字符数组中的字符串数据.从给定的数组的地址开始 1个字节1个字节的输出,直到        遇到'\0'为止.

    使用scanf函数接收用户输入的字符数据

   1). 先准备1个字符数组来保存用户输入的字符串数据.

        char str[10];

    2). 使用scanf函数接收用户输入字符串数据并存储到指定的字符数组中.

           scanf("%s",str);

注意:

    1). 如果输入的字符串数据在给定的字符数组中存储不下的时候,就会运行报错

    2). 用户在输入字符串的时候,如果输入了空格,就会认为输入结束.

 使用以上两种函数有很多的不足,所以常用的是以下函数

 

 1. puts()函数.

    作用:用来输出字符串的.

    语法格式: puts(存储字符串的字符数组名);

    优点:输出完毕之后,自动换行.

    缺点:只能输出字符串.也不能使用占位符.

 

 

 2. gets()函数

    作用: 从控制台接收用户输入1个字符串数据.

    语法格式: gets(存储字符串的字符数组名);

    优点: 当用户输入的数据包含空格的时候 它会连空格一起接收.

 

    缺点:和scanf函数一样 不安全.

        当用来存储字符串的数据的字符数组的长度不够的时候 程序就会崩溃.

 

 ------下面四个函数 是声明在string.h这个头文件中------

 

 3. strlen();函数

 

    作用: 得到存储在字符数组中字符串数据的长度.

    语法格式:   strlen(字符串);

 4. strcmp()函数.

    cmp --> compare 比较.

    作用: 用来比较两个字符串的大小的.

    语法格式:

    strcmp(字符串1,字符串2);

    返回值是int类型的.

    

    如果返回的是负数.就说明字符串1比字符串2小.

    如果返回的是正数 就说明字符串1比字符串2大.

    如果返回的是0 就说明一样.

    比较的规则:比的是相同位置的字符的ASCII码的大小.

 5. strcpy()函数  copy

 

    作用: 把存储在1个字符数组中的字符串数据拷贝到另外1个字符数组中存储.

    格式:

    strcpy(字符串1,字符串2);

    将字符串2拷贝到字符串1数组中.

    可能的问题.

    存储字符串1的字符数组长度不够,无法存储字符串2 这个时候运行就会崩溃.

 

 6. strcat()函数. concat: 连接.

    语法格式:

    strcat(字符数组1,字符数组2);

    作用: 把存储在字符数组2的字符串数据链接在字符串1的后面.

         将两个字符串合成1个字符串.

    存在的问题:

    如果字符数组1中无法存储下字符数组2的字符串数据,运行就会报错.

使用注意.

    1). 如果我们使用字符数组存储字符串数据的时候,没有指定这个字符数组的长度.

        那么这个时候,这个字符数组的长度 字符串的长度 + 1

    2). 如果我们使用字符数组存储字符串数据的时候,指定了这个字符数组的长度.

        那么这个字符数组的长度就是我们指定的长度.

    3). 如果指定的字符数组的长度小于等于了字符串的长度,这个时候就会有问题了.

        就会出现存储不下的情况.

    4). 所以,我们在使用字符数组存储字符串数据的时候,最好就不要指定长度了,让让知己算 这样刚合适.

    5). 如果在声明1个字符数组的同时我们就初始化1个字符数据给这个数组,

        那么这个时候是可以用中文.

        1个中文占据3个字符.

 

 

7 指针相关

1.什么是指针

    变量的地址就叫做指针. 指针就是地址.地址就是指针.

2.指针变量

  指针变量就是专门用来存储地址的变量

 2.1指针变量的声明

      数据类型*指针变量的名称;

      批量声明指针变量 int* p1, *p2, *p3;

      指针变量是用来存储另外1个变量的地址.

        但是1个指针变量并不是可以存储 任意类型的变量的地址.而是有限定的.

        只能存储和这个指针类型相同的普通变量的地址.

      使用指针间接的操作指针指向的变量.

        *p  *(p+1)

           格式: *指针变量名; 代表这个指针指向的变量.

          int num = 10;

          int* p1 = #

          *p1 代表p1指针指向的变量.也就是num

          *p1 完全等价于 num

          *指针变量名; 这个表达式的结果是指针指向的变量.拿到看指针指向的变量.

                     就可以为指针指向的变量赋值或者取值.

        *指针变量就完全代表指针指向的变量.

          所以通过这种方式为指针指向的变量赋值的时候数据类型不同的时候 会做自动类型转换.

  2.2 野指针

  

         我们声明1个指针变量,如果没有为其初始化.那么这个时候这个指针变量中是有值的.垃圾值.随机数

         这个时候,这个指针变量就有可能指向了1块随机的空间.

         这块空间: 有可能无人使用.有可能别的程序在用 有可能系统在用.

         这个时候,去访问指针指向的变量的时候,就会报错. BAD_ACCESS错误.

         像这样的指针我们就叫做野指针.

         NULL值.是指针变量的值.

         我们声明1个指针,如果不初始化这个指针.这个指针就是1个野指针就指向了1块随机的空间.

3 指针变量的作用

   

    当函数的参数的类型是数组的时候,这个时候参数传递是地址传递.

     在函数内部修改参数数组的元素的时候,其实修改的就是实参数组的元素.

   指针是一种新的数据类型.

     指针可以不可以作为函数的参数呢?

     1).指针完全当然可以作为函数的参数,因为指针也是1个数据类型.

         直接将指针的声明放在小括弧中.

     2).当我们调用1个函数的时候.如果这个函数的参数是1个指针.

         那么我们就必须要为这个指针传递1个和指针类型相同的普通变量的地址.

     3).这个时候,在函数的内部去访问参数指针指向的变量的时候,其实访问的就是实参变量.

    5. 指针作为函数的参数,可以实现什么效果?

     函数的内部可以修改实参变量的值.

    6. 什么时候需要将指针作为函数的参数?

     1). 遇到的问题.

         函数只能返回1个数据.

     2). 解决方案.

         使用指针作为函数的参数.让调用者将自己的变量的地址传递到函数的内部

         函数的内部通过指针就可以修改实参变量的值.

     3).当函数需要返回多个数据的时候就可以使用指针作为函数的参数.

 

1.指针为什么要分类型

    无论指针是什么类型,在内存中都是占据8个字节.既然指针都是占据8个字节.为什么指针还要分类型

   通过指针间接的操作指针指向的变量的方式.

    int num = 10;

    int* p1 = #

    p1指针变量中存储的是num变量的地址也就是num变量的低字节的地址.

    通过这个p1指针其实只能找到这个地址的字节.

    这个时候,通过p1指针找到这个字节操作的时候,是操作多个字节空间呢?

    操作多少个字节是根据指针的类型来决定的.

    指针变量的类型决定了通过这个指针找到字节以后连续操作多少个字节空间.

    指针是int*  那么就连续操作4

    指针是double*那么就连续操作8

    指针是float* 那么就连续操作4

    指针是char* 那么就连续操作1

    所以,指针的类型如果不和指向的变量的类型相同话.那么通过指针就无法正确的操   作指向的变量.

    所以,指针变量一定要指向1个和自己类型相同的普通变量才可以.

2.多级指针

    一级指针.

    1个指针变量中存储的是1个普通变量的地址,像这样的指针,我们就叫做一级指针.

    二级指针.

    1个指针变量中存储的是1个一级指针变量的地址,像这样的指针,我们就叫做二级指针.

    三级指针.

    1个指针变量中存储的是1个二级指针变量的地址,像这样的指针,我们就叫做三级指针.

多级指针定义.

 

    1).只能存储一级指针变量的地址.这样的指针叫做二级指针.;

    2).声明二级指针的语法:

        a. 声明一级指针:数据类型* 指针名;

        b. 声明二级指针:数据类型** 指针名;

        c. 声明三级指针:数据类型*** 指针名;

    4).一级指针只能存储普通变量的地址.

        二级指针只能存储一级指针变量的地址

        三级指针只能存储二级指针变量的地址.

        n级指针只能存储n-1级指针变量的地址.

        否则就会出问题.

    5). 初始化.

         使用取地址运算符.拿到对应的指针变量的地址赋值给指针变量就可以了.

         int num = 10;

         int* p1 =# //p1是1个一级指针.存储的是num变量的地址.

         int** p2 =&p1; //p2是1个二级指针,存储的是一级指针变量p1的地址.

         int*** p3 =&p2;//p3是1个三级指针,存储的是二级指针变量p2的地址.

    多级指针使用:

      使用指针的时候.有几颗星就拐几个弯.

        *指针变量名.代表这个指针指向的变量.

        **指针变量名.这个指针至少要是1个二级指针.代表这个指针指向的指针指向的变量.

        *p1  代表p1指针指向的变量.

        **p1  代表p1指针指向的指针指向的变量.

        ***p1 代表p1指针指向的指针指向指针指向的变量.

       1. n级指针只能存储n-1级指针变量的地址.

       2. 使用指针间接访问指向的变量的时候.

             有几颗星就拐几个弯.

3指针加减法

    指针与整数进行加减运算.

    比如指针 + 1;

    并不是在指针地址的基础之上加1个字节的地址.而是在这个指针地址的基础之上加1个单位变量占用的字节数.

    如果指针的类型是int* 那么这个时候1代表4个字节地址.

    如果指针的类型是double* 那么这个时候1代表8个字节地址.

    如果指针的类型是float* 那么这个时候1代表4个字节地址.

    如果指针的类型是char* 那么这个时候1代表1个字节地址.

 

使用指针遍历数组

  

    1).使用指针遍历数组的第一种方式.

         int arr[7] ={10,20,30,40,50,60,70};

         int* p1 = arr;//p1指针指向了数组的第0个元素.

         

         for(int i =0; i < 7; i++)

         {

               printf("%d\n",*(p1+i));

            }

 

     2).使用指针遍历数组的第二种方式.

         

         int arr[7] ={10,20,30,40,50,60,70};

         

         for(int i =0; i < 7; i++)

         {

               printf("%d\n",*(arr+i));

            }

         

     3).使用指针遍历数组的第三种方式.

 

         int arr[7] ={10,20,30,40,50,60,70};

         int* p1 = arr;

         

         for(int i =0; i < 7; i++)

         {

               printf("%d\n",*(p1++));

            }

         注意的地方,每次循环.p1的值都会变化.

         最后1次执行完毕之后. p1指针指向外面去了.

         p1就不再执行数组中的任何元素了.

3数组作为函数参数

       在声明这个参数数组的时候.它并不是去创建1个数组而是去创建1个用来存储地址的指针变量.

      如果我们为函数写了1个数组作为参数.

      其实啊,编译器在编译的时候,已经把这个数组换成了指针.

      所以.在声明参数的时候不是创建数组,而是创建1个存储数组地址的指针变量.

      这也就是为什么我们通过sizeof计算参数数组得到的永远都是8.

      所以.以后,我们的函数如果带了1个参数这个参数是1个数组.

      建议,不要写数组了,直接写1个指向数组的第0个元素的指针,=再传入长度

4索引的本质

   1).指针变量后面可以使用中括弧,在中括弧中写上下标来访问数据.

   2). p1[n];前提是p1是1个指针变量.

        完全等价于 *(p1+n);

   3).只要是指针都可以使用中括弧下标.就相当于是去访问指针指向的变量.

        操作数组我们虽然使用的中括弧下标来操作,实际上内部仍然使用的指针来操作.

1指针数组

    如果1个数组是用来存储指针类型的数据的话.那么这个数组就叫做存储指针的数组

     元素类型 数组名[数组长度];

     int* arr[3];

     这个arr数组的元素的类型是int*. 是int指针,

     所以这个数组可以存储int指针数据.最多存储3个.

2.指针之间的减法运算

   

    1. 指针与指针之间可以做减法运算.

        结果是1个long类型的数据.

        结果的意义: 代表两个指针指向的变量之间相差多少个单位变量.

        绝大多数情况下. 我们用在判断数组的两个元素之间相差多少个元素.

 

    2. 如果参与减法运算的两个指针不指向同1个数组,结果就有问题.;

       两个指针变量的相减的结果:

       先求出两个指针的差 / 每1个指针变量对应的普通变量占用的字节数 

 

   3. 唯一的意义.

 

    就是用在数组中,判断两个元素之间相差多少个元素.

 

   4. 指针与指针之间只能做减法运算.

3指针之间的比较运算

  

   1. 指针与指针之间可以做比较运算.

    都可以作用于两个指针之间.

 

  2. 为变量分配字节空间的时候.

     从高地址向低地址分配的嘛.

     > >= < <=  它可以判断两个指针指向的变量的地址 谁在高字节 谁在低字节.

 

  3. 也可以使用==、!= 来判断两个指针指向的地址是不是为同1个地址.

 

内存分类相关

 
7 内存的五大区域.

 

    栈: 局部变量.

    堆:堆区中的字节空间允许程序员手动的申请.

    BSS段:未初始化的全局变量、静态变量.

    数据段:已经初始化的全局变量、静态变量和 常量数据.

    代码段:存储代码的.

    注意:常量数据只保存一份,保存常量数据的时候会先检测数据段是否已有要保存的常量数据,如果有则用原来的,如果没有则新开辟空间保存常量数据。

 

2. 如何向堆区申请字节空间来使用.

 

    1).我们在堆中申请的字节空间.如果我们不主动释放.那么系统是不会释放到的.除非程序结束了.

 

    2).在堆中申请字节空间的步骤.

        

        a. 申请.

        b. 使用.

        c. 释放.

 

    3).如何在堆区申请指定字节数的字节空间呢?

 

       malloc()

       calloc()

       realloc()

    

       这3个函数都是和申请字节空间有关的. 这几个函数的声明 是放在1个叫做stdlib.h的系统头文件中.

 

    4). malloc函数.

        0).作用: 向堆空间申请指定字节的空间来使用.

        1). 参数只有1个:size_t类型的也就是unsigned long.

            参数代表的意义: 向堆内存申请多少个连续的字节空间.

        2).做的事情: 在堆内存中申请连续的参数个字节空间.

        3). 返回值是: void *.

            void *代表.没有类型的指针.

            返回的是创建的空间中第1个字节的地址.

            地址没有类型的. 只是返回了第1个字节的地址.没有说这个指针是什么类型的.

 

        4).我们应该使用什么类型的指针变量来保存malloc函数返回的地址呢?

            那就要看你想要如何去操作申请的这些字节空间.

            如果你想要1个字节1个字节的操作.那么就使用char指针.

            如果你想要4个字节4个字节的操作.并且当做整型来操作那么就是要int指针.

            如果你想要8个字节8个字节的操作.那么就是要double指针.

            如果你想要4个字节4个字节的操作.并且当做单精度浮点型来操作那么就是要float指针.

就要看你想如何操作申请的这些字节空间.

 

         5).在堆区申请的字节空间是从低地址向高地址分配.

             每次申请的字节地址都是从0开始.每一次申请的字节空间不一定挨在一起.

             但是. 每一次申请的指定个字节这些字节一定肯定决定是连续的.

         6).在堆区申请的字节,里面是有值的.值是垃圾值.不会自动清零.

         7).在向堆区申请字节空间的时候,有可能会申请失败.

             如果申请失败返回的指针就是NULL值.

             我们申请完空间之后,最好是判断1下.判断是否申请成功.

             int*p1 = malloc(4);

             if(p1)

             {

                   //代表申请成功,要做的事情放这就可以了.

                }

        8).申请的空间使用完毕之后,一定要记得释放.

 

            释放申请的堆空间:

            free(指针);

            如果没有free程序结束才会释放.

            记住,一定要释放. 因为我们的程序有可能会运行很久.

            如果非要等到结束的时候自动释放 那就晚了.

             

 3. calloc函数.

 

    1).作用: 向堆区申请指定字节数的空间.

    2). 格式:

        参数1: 多少个单位.

        参数2: 每1个单位的字节数.

 

        calloc(4,sizeof(int));

        表示申请4个int空间.

 

    3). 与malloc的优势

 

         calloc申请的字节,申请完之后,系统会将字节中的数据清零.

 

 4. realloc函数.

 

    1). 作用: 扩容.

    2).注意: 我们有了指针 几乎就可以操作内存上的每一个字节.

             但是我们还是建议,不要乱来. 只操作我们申请的字节空间.

             因为.有可能会出现一些问题.

    3).当我们发现我们之前在堆区申请的字节空间不够用的时候.

        就可以使用realloc函数来为我们的字节空间扩容.

        a. 如果原来的空间后面还有剩余的空间.并且足够扩容.那么直接就扩容在屁股后面.

 

        b. 如果原来的空间后面有剩余空间但是剩下的空间不够扩容.就重新找1块足够的空间申请.

           将原来的数据拷贝过来. 原来的空间被自动释放.

 3. 字符串数据在C语言中的存储.

    1).使用字符数组来存储.

        将字符串数据的每一个字符,存储到字符数组中,并追加1个'\0'代表存储结束,

        char name[5] ={'j','a','c','k','\0'};

        char name[] ={'j','a','c','k','\0'};

        char name[] ={"jack"};只有直接给字符串数组初始化1个串的时候,才会自动追加1个'\0'.前提是字符数组的长度足够的情况下.

        char name[] ="jack";

    2).使用字符指针来存储字符串数据.

        直接将1个字符串数据初始化 给1个字符指针.

        char* name ="jack";

        虽然我们不知道这个的原理.但是有1点值得肯定.

        绝对不是将"jack"存储到指针变量name中.

        name是1个指针变量.只能存储地址.

 4. 存储字符串的两种方式的区别.

 

    1).使用字符数组来存储: 将字符串数据的每1个字符存储到字符数组的元素中,追加1个'\0'表示结束.

        char name[] ="jack";

    2).使用字符指针来存储: 直接为字符指针初始化1个字符串数据.

        char *name ="jack";

    ------------

    1).当他们都是局部变量的时候.

         char name1[] ="jack";

         char *name2 ="rose";

 

        a. name1字符数组.是申请在栈区.字符串的每一个字符存储在这个字符数组的每1个元数中.

        b. name2是1个局部的指针变量,name2这个指针变量是声明在栈区的.

           这个时候, 字符串数据是存储在常量区中的.

           字符串数据就是以字符数组的形式存储在常量区.

           name2指针变量中.存储的是"rose"这个字符串在常量区的地址.

 

    2).当他们作为全局变量的时候.

         char name1[] ="jack";

         char* name2 ="rose";

 

         a. name1字符数组是存储在常量区的.字符串的 每1个字符是存储在这个数组中的每一个元素中.

         b. name2指针也是存储在常量区的.字符串也是以字符数组的形式存储在常量区.

            name2指针中存储的是 "rose"这个字符串在常量区的地址.

    这两种方式的区别.

    在内存中存储的结构是不同的.

    a. 以字符数组存储:无论如何是1个字符数组.然后字符串的每一个字符存储在数组的元素之中.

    b. 以字符指针存储:无论如何首先有1个字符指针变量.字符串数据是以字符数组的形式存储在常量区的.

   

 

    1).以字符数组存储的字符串数据,可以修改字符数组的元素.

        以字符数组的形式存储字符串数据,不管是全局的还是局部的,都可以使用下标去修改字符数组中的每一个元素.

    2).以字符指针的形式存储字符串数据.这个时候字符指针指向的字符串数据是无法修改的.

        以字符指针的形式存储字符串数据,不管是全局的还是局部的,都不能通过指针去修改指向的字符串数据.

 

8  预处理指令

 

1). 都是以#开头.

    2). 预处理指令都是在编译之前执行.

    3). 预处理指令后面都没有分号.

2. 预处理指令的分类.

    1). 文件包含指令  #include

    2). 宏定义: 可以将1段C代码定义为1个标识,使用这个标识就可以使用这段代码.

    3). 条件编译指令: 只编译指定的C代码为二进制指令.

3 预处理指令详解

   3.1#include命令详解

         #include后可以包含任意类型,不仅.h可以,也可以.c,也可以.cpp。也就是其他的代码文件可以利用#include直接调用。

         #include内容不能重复包含,将会出现变量重名。即#include后所包含内容重复,不行。如aq.cpp包含date.h,若include.cpp调用了aq.cpp,就不需要再调用date.h了,否       则将出现变量重名,编译出错。变量的定义可以在头文件中,变量的运算可以在原文件中。也就是可以分开放。一般情况下,函数的声明,应该放在头文件。函数的实现        与变量的定义应当放在源文件。

    3.2 宏定义

          宏定义的作用:以为1段C代码定义1个标识.如果你要使用这d段C代码.那么你就使用这个标识就可以了.它是1个预处理指令.所以它在编译之前执行.

       语法:

             #define 宏名 宏值

             #define N 10

         宏的原理

              在预编译的时候,就会执行源文件中的预处理指令.

              会将C代码中使用宏名的地方替换为宏值.

将C代码中的宏名替换为宏值的过程叫做  宏替换/宏代换.      

 

    #define  和  typedef 的区别

        1). #define是1个预处理指令.在预编译的时候执行. 在预编译的时候会吧宏名换成宏值.

              typedef 是1个C代码. 在运行的时候才会执行.

         2).  #define 可以将任意的C代码取1个标识名.

              typedef只能为数据类型取名字.

     3.3条件编译指令

         3.3.1. 条件编译指令.

             1). 它是1个预处理指令. 所以在预编译阶段执行.

             2). 作用:

                 默认的情况下,我们所有的C代码都会被编译为二进制代码.

                条件编译指令的作用: 可以让编译器只编译指定部分的代码.

 

           3.3.2. 条件编译指令的用法.

               #if 条件

                  C代码;

              #endif

             

 对应PPT,源码链接


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值