C语言深度扩展——1. 数据类型及关键字(下)

文章目录

  • (6). goto、void、extern、sizeof
  • (7). const、volatile
  • (8). 自定义类型(struct、 union、 enum)
  • (9). typedef

(6). goto、void、extern、sizeof

  1. goto(跳转,禁用)

    • 高手潜规则:禁用goto

    • 项目经验:程序质量与goto的出现次数成反比

    • 最后的判决:将goto打入冷宫

      #include 
      
      void func(int n)
      {
          int* p = NULL;
          
          if( n < 0 )
          {
              goto STATUS;   //内存泄漏
          }
      
          P = (int*)malloc(sizeof(int) * n);
          
      STATUS:
          p[0] = n;
          
          free(p);
      }
      
  2. void (表示无)

    1. void修饰函数返回值和参数(仅表示无)

      • 如果函数没有返回值,那么应该将其声明为void型

      • 如果函数没有参数,应该声明其参数为void

    2. 不存在void变量

        C语言没有定义void究竟是多大内存的别名(没有void的标尺无法在内存中裁剪出void对应的变量)

       #include 
       
       int main()
       {
           printf("%d\n", sizeof(void));
           
           return 0;
       }
      

      小贴士:
        上述代码在 ASNI C 编译器中是无法通过编译,但在支持 GNU 标准的 gcc 编译器而言是合法的

    3. void指针的意义:

      • C语言规定只有相同类型的指针才可以相互赋值

      • void*指针作为左值用于“接收”任意类型的指针

      • void*指针作为右值赋值给其它指针时需要强制类型转换

        #include 
        
        void* my_memset(void* p, char v, int size)
        {
            void* ret = p;
            char* dest = (char*)p;
            int i = 0;
            
            for(i = 0; i < size; i++)
            {
                dest[i] = v;
            }
            
            return ret;
        }
        
        int main()
        {
            int a[5] = {1, 2, 3, 4, 5};
            int i = 0;
            long l = 10000;
            
            for( i = 0; i < 5; i++)
            {
                printf("a[%d] = %d\n", i, a[i]);
            }
            
            my_memset(a, 0, sizeof(a));
            
            for( i = 0; i < 5; i++)
            {
                printf("aset[%d] = %d\n", i, a[i]);
            }
            
            printf("l = %d\n", l);
            
            my_memset(&l, 0, sizeof(l));
            
            printf("lset = %d\n", l);
            
            return 0;
        }
        

C语言深度扩展——1. 数据类型及关键字(下)_第1张图片

  1. extern(声明外部定义)

    1. extern用于声明外部定义的变量和函数

      extern变量在文件的其他地方定义和分配空间

         // s2.c 
         int g = 100;
         
         int get_min(int a, int b)
         {
             return (a < b)? a : b;
         }
      
         // extern.c
         #include 
         
         extern int g;
         extern int get_min(int a, int b);
         
         int main()
         {
             printf("%d\n", g);
             printf("%d\n", get_min(1, 5));
             
             return 0;
         }
      

C语言深度扩展——1. 数据类型及关键字(下)_第2张图片

  1. extern用于“告诉”编译器用C方式编译

      C++编译器和一些变种C编译器默认会按“自己”的方式编译函数和变量,通过extern关键可以命令编译器“以标准C方式进行编译”。

      extern "C"
      {
          int f(int a, int b)
          {
              return a + b;
          }
      }
    
  2. sizeof(“计算”大小)

    1. sizeof 是编译器的内置指示符,不是函数

      • 在编译过程中所有的 sizeof 将被具体的数值所替换

      • 程序的执行过程与 sizeof 没有任何关系

    2. sizeof 用于“计算”相应实体所占的内存大小

      • sizeof用于类型: sizeof(type)

      • sizeof用于变量:sizeof(var) 或 sizeof var

    3. sizeof 的值在编译期就已经确定

      #include 
      
      int f()
      {
          printf("\n");
          
          return 0;
      }
      
      int main()
      {
          int var = 0;
          int size = sizeof(var++);
          
          printf("var = %d, size = %d\n", var, size);
          
          size = sizeof(f());
          
          printf("size = %d\n", size);
          
          return 0;
      }
      

C语言深度扩展——1. 数据类型及关键字(下)_第3张图片

(7). const、volatile

  1. const(定义的不是真的常量)

      “const”含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。在现在C语言编译器中,将const修饰的全局生命周期的变量(全局变量、static修饰的变量)存储于只读存储区中,修改将会导致程序崩溃,而标准C语言编译器则存储于可修改的全局数据区,其值依然可以改变。
    C语言深度扩展——1. 数据类型及关键字(下)_第4张图片

    1. const 修饰变量

      • 在C语言中const修饰的变量是只读的(不能作为左值),其本质还是变量

      • 本质上const只对编译器有用,在运行时无用

      • const修饰的局部变量在栈上分配空间,修饰的全局变量在全局数据区分配空间,故可以通过指针改变值

        #include 
        
        const int bb = 0;
        
        int main()
        {
            const int cc = 1;
            int* p = (int*)&cc;
            int* p1 = (int*)&bb; 
            
            printf("cc = %d\n", cc);
            
            cc = 3; //error
            
            printf("cc = %d\n", cc);
            
            *p = 3;
            
            printf("cc = %d\n", cc);
            
            *p1 = 3; //error,存储于只读存储区
            
            printf("cc = %d\n", cc);
            
            return 0;
        }
        

C语言深度扩展——1. 数据类型及关键字(下)_第5张图片

  1. const修饰数组

    • 在C语言中const修饰的数组是只读的

    • const修饰的数组空间不可被改变(针对现在C编译器而言[程序会死掉],标准C是可以通过指针改变的)

  2. const修饰指针

    • const int* p;       p可变,p指向的内容不可变

    • int const* p;       p可变,p指向的内容不可变

    • int* const p;       p不可变,p指向的内容可变

    • const int* const p;    p 和 p指向的内容都不可变

      口诀:左数右指

      当const出现在*号左边时指针指向的数据为常量

      当const出现在*后右边时指针本身为常量

      左数:

      #include 
      
      int main()
      {
          int i = 0, t = 0;
          
          const int* p = &i;
          int const* y = &t;
          
          *p = 3;
          *y = 3;
          
          return 0;
      }
      

      C语言深度扩展——1. 数据类型及关键字(下)_第6张图片

      左数右指:

      #include 
      
      int main()
      {
          int i = 0;
          const int* const p = &i;
          
          *p = 3;
          
          p = NULL;
          
          return 0;
      }
      

      C语言深度扩展——1. 数据类型及关键字(下)_第7张图片

  3. const修饰函数参数和返回值

    • const修饰函数参数表示在函数体内不希望改变参数的值

    • const修饰函数返回值表示返回值不可改变,多用于返回指针的情形

    小贴士:

      C语言中字符串字面量存储于只读存储区中,在程序中需要使用 const char* 指针。

    #include 
    
    const char* f(const int i)
    {
        i = 5; // error
        
        return "Delphi Tang";
    }
    
    int main()
    {
        char* pc = f(0);
        
        printf("%s\n", pc);
        
        pc[6] = '_'; //error
        
        printf("%s\n", pc);  
        
        return 0;
    }
    

C语言深度扩展——1. 数据类型及关键字(下)_第8张图片

  1. volatile(防止优化)

      “volatile”的含义是“请不要做没谱的优化,这个值可能变掉的”,而并非“你可以修改这个值”。

    1. volatile 可理解为“编译器警告指示字”,用于告诉编译器必须每次去内存中取变量值

    2. volatile 主要修饰可能被多个线程访问的变量,也可以修饰可能被未知因数更改的变量

       int obj = 10;
       
       int a = 10;
       int b = 0;
       
       a = obj;
       
       sleep(100);
       
       b = obj;
      

        编译器在编译的时候发现 obj 没有被当成左值使用,因此会“聪明”的直接将 obj 替换成 10,把 a 和 b 都赋值为10,如果程序运行到 sleep(100); 处,硬件中断改变了内存中 obj 的值,而 b = obj; 没从内存取值,会使程序出现错误。

  2. const volatile int i = 0; 这个时候i具有什么属性?编译器如何处理这个变量?

      答:没问题,const 和 volatile 这两个类型限定符不矛盾。const表示(运行时)常量语义:被 const 修饰的对象在所在的作用域无法进行修改操作,编译器对于试图直接修改 const 对象的表达式会产生编译错误。volatile表示“易变的”,即在运行期对象可能在当前程序上下文的控制流以外被修改(例如多线程中被其它线程修改;对象所在的存储器可能被多个硬件设备随机修改等情况):被 volatile 修饰的对象,编译器不会对这个对象的操作进行优化。一个对象可以同时被 const 和 volatile 修饰,表明这个对象体现常量语义,但同时可能被当前对象所在程序上下文意外的情况修改。

(8). 自定义类型(struct、 union、 enum)

  1. struct(结构体)

    1. 基本知识

      引用相应的结构成员( . 和 -> )

      • 引用某个特定结构中的成员: 结构名.成员

      • 假定p是一个指向结构的指针: p->结构成员

    2. 空结构体占用多大内存?

        这是C语言的一个灰色地带,没有明确的说明,不同的编译器有不同的实现方式,没有哪个对错,只有哪个更为合理。(下面分别用GCC和G++进行编译的结果)

         #include 
         
         struct _null
         {
         };
         
         int main()
         {
             struct _null n1;
             struct _null n2;
             
             printf("%d\n", sizeof(struct _null));
             printf("%d, %0X\n", sizeof(n1), &n1);
             printf("%d, %0X\n", sizeof(n2), &n2);
             
             return 0;
         }
      

      C语言深度扩展——1. 数据类型及关键字(下)_第9张图片

    3. 由结构体产生柔性数组

      1. 柔性数组即数组大小待定的数组

      2. C语言中结构体的最后一个元素可以是大小未知的数组

      3. C语言中可以由结构体产生柔性数组

         (数组大小已知在栈上自动分配,未知大小则在动态的空间里面堆上进行申请)

          空柔性数组大小:(SoftArray中的array仅是一个待使用的标识符,不占用存储空间)

      C语言深度扩展——1. 数据类型及关键字(下)_第10张图片

      	    #include 
      	    #include 
      	    
      	    struct SoftArray
      	    {
      	        int len;
      	        int array[];
      	    };
      	    
      	    int main()
      	    {
      	        struct SoftArray* sa = NULL;
      	    
      	        printf("%d\n", sizeof(sa));
      	        
      	        sa = (struct SoftArray*)malloc(sizeof(struct SoftArray) + sizeof(int) * 5);
      	        
      	        sa->len = 5;
      	        
      	        printf("%d\n", sizeof(sa));
      	    }
    

    C语言深度扩展——1. 数据类型及关键字(下)_第11张图片
    4. 柔性数组的使用——存储斐波拉次数列

     (斐波拉次数列:前两个元素为1,以后每项为前两项相加之和)

    	#include 
    	#include 
    	
    	struct SoftArray
    	{
    	    int len;
    	    int array[];
    	};
    	
    	struct SoftArray* create_soft_array(int size)
    	{
    	    struct SoftArray* ret = NULL;
    	    
    	    if( size > 0 )
    	    {
    	        ret = (struct SoftArray*)malloc(sizeof(struct SoftArray) + sizeof(int) * size);
    	        
    	        ret->len = size;
    	    }
    	    
    	    return ret;
    	}
    	
    	void delete_soft_array(struct SoftArray* sa)
    	{
    	    free(sa);
    	}
    	
    	void func(struct SoftArray* sa)
    	{
    	    int i = 0;
    	    
    	    if( NULL != sa )
    	    {
    	        for(i=0; i<sa->len; i++)
    	        {
    	            sa->array[i] = i + 1;
    	        }
    	    } 
    	}
    	
    	int main()
    	{
    	    int i = 0;
    	    struct SoftArray* sa = create_soft_array(10);
    	    
    	    func(sa);
    	    
    	    for(i=0; i<sa->len; i++)
    	    {
    	        printf("%d\n", sa->array[i]);
    	    }
    	    
    	    delete_soft_array(sa);
    	    
    	    return 0;
    	}   
    

    C语言深度扩展——1. 数据类型及关键字(下)_第12张图片
    5. 用memcmp()比较结构体

      可以通过memcmp()来比较2个相同的结构体变量,但这2个变量必须在赋值之前进行清零初始化(否则结果不准确),或者2者是通过直接对等赋值而来。另外,结构体的命名对memcmp()没有影响(只要内部结构一致)

  2. union(联合声明)

    1. union和struct的区别

      • struct中的每个域在内存中都独立分配空间

      • union只分配最大域的空间,所有域共享这个空间(任意时刻它最多只能存储其中的一个成员)

          #include 
          
          struct A
          {
              int a;
              int b;
              int c;
          };
          
          union B
          {
              int a;
              int b;
              int c;
          };
          
          int main()
          {
              printf("sizeof(struct A) = %d\n",sizeof(struct A));
              printf("sizeof(union B) = %d\n",sizeof(union B));
              
              return 0;
          }
        

      C语言深度扩展——1. 数据类型及关键字(下)_第13张图片

    2. union和struct联合用法

        通常情况,我们无法检查联合的某一个成员,除非已用该成员给联合赋值。但是,有一个特殊情况可以简化联合的使用:如果一个联合包含共享一个公共初始序列的多个结构,并且该联合当前包含这些结构中的某一个,则允许引用这些结构中任意结构的公共初始化部分。

    	   union{
    	       struct {
    	           int type;
    	       } n;
    	       
    	       struct {
    	           int type;
    	           int intnode;
    	       } ni;
    	       
    	       struct {
    	           int type;
    	           float floatnode;
    	       } nf;
    	   } u;
    	   
    	   ...
    	   u.nf.type = FLOAT;
    	   u.nf.floatnode = 3.14;
    	   ...
    	   
    	   if(u.n.type == FLOAT)
    	       ...sin(u.nf.floatnode)...
    	       
    
    1. union使用的注意事项

      union的使用受系统大小端的影响

      #include 
      
      int system_mode()
      {
          union SM
          {
              int i;
              char c;
          };
      
          union SM sm;
          
          sm.i = 1;
          
          return sm.c;
      }
      
      
      int main()
      {
          printf("System Mode: %d\n", system_mode());
          return 0;
      }
      
      

      C语言深度扩展——1. 数据类型及关键字(下)_第14张图片
      C语言深度扩展——1. 数据类型及关键字(下)_第15张图片

  3. enum(枚举类型)

    1. 基本知识

      • enum是一种自定义类型,定义的值是C语言中真正意义上的常量。

      • enum默认常量在前一个值的基础上依次加1,默认第一个常量为0

      • enum类型的变量只能取定义时的离散值

        #include 
        
        enum color
        {
            GREEN,
            RED = 2,
            BULE
        };
        
        int main()
        {
            printf("GREEN = %d\n", GREEN);
            printf("RED = %d\n", RED);
            printf("BULE = %d\n", BULE);
            
            return 0;
        }
        

      C语言深度扩展——1. 数据类型及关键字(下)_第16张图片

    2. 枚举类型和#define的区别

      • #define宏常量只是简单的进行值替换,枚举常量是真正意义上的常量

      • #define宏常量无法被调试,枚举常量可以

      • #define宏常量无类型信息,枚举常量是一种特定类型的常量

(9). typedef

  1. 基本概念

    1. typedef用于给一个已经存在的数据类型重命名

    2. typedef并没有产生新的类型

    3. 重定义的类型

      • 可以在typedef语句之后定义

      • 不能进行unsigned和signed扩展

      #include 
      
      typedef int Int32;
      
      struct _tag_point
      {
          int x;
          int y;
      };
      
      typedef struct _tag_point Point;
      
      typedef struct
      {
          int length;
          int array[];
      } SoftArray; 
      
      typedef struct _tag_list_node ListNode;
      struct _tag_list_node
      {
          ListNode* next;
      };
      
      int main()
      {
          Int32 i = -100;        // int 
          unsigned Int32 ii = 0; //error
          Point p;               // struct _tag_point
          SoftArray* sa = NULL;   
          ListNode* node = NULL; // struct _tag_list_node*
          
          return 0;
      }
      

C语言深度扩展——1. 数据类型及关键字(下)_第17张图片

  1. typedef和#define的区别

    1. typedef是给已有类型取别名

    2. #define为简单的字符串替换,无别名的概念

      // demo1 
      #include 
      
      typedef char* PCHAR;
      
      int main()
      {
          PCHAR p1, p2;
          
          char c;
          
          p1 = &c;
          p2 = &c;
          
          return 0;
      }
      
      // demo2 
       #include 
       
       #define PCHAR char*
       
       int main()
       {
           PCHAR p1, p2;
           
           char c;
           
           p1 = &c;
           p2 = &c;
           
           return 0;
       }
      

      由上面对比可知,demo1 :typedef为重命名,故 p1, p2 都为 char*;demo2:#define 为简单的字符串替换,PCHAR p1, p2;替换完为 char* p1, p2;(其中 p1 为 char 型指针,p2 为 char 型变量),故才出现错误。

你可能感兴趣的:(C语言深度扩展)