C 语法2 - 数据类型/常变量


改编自 https://www.runoob.com/cprogramming/c-data-types.html


类型概览

C 中的类型可分为以下几种:

  1. 基本类型:整数类型和浮点类型。
  2. 枚举类型
  3. void 类型
  4. 派生类型:指针类型、数组类型、结构类型、共用体类型 和 函数类型(函数返回值的类型)。

数组类型和结构类型统称为聚合类型


类型的大小

变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。

各种类型的存储大小与系统位数有关,您可以使用 sizeof 运算符得到准确的大小。


位、字节、字

位是最小的存储单位,每一个位存储一个1位的二进制码
一个字节由8个位组成。如 01001011
字通常为16、32或64个位组成;即2个字节,4、8个字节。


一、整数类型

类型存储大小值范围
char1 字节-128 到 127 或 0 到 255
unsigned char1 字节0 到 255
signed char1 字节-128 到 127
int2 或 4 字节-32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int2 或 4 字节0 到 65,535 或 0 到 4,294,967,295
short2 字节-32,768 到 32,767
unsigned short2 字节0 到 65,535
long4 字节-2,147,483,648 到 2,147,483,647
unsigned long4 字节0 到 4,294,967,295

#include <stdio.h>
#include <limits.h>
 
int main()
{
   printf("int 存储大小 : %lu \n", sizeof(int)); 
   return 0;
}

二、浮点类型

标准浮点类型的存储大小、值范围和精度的细节:

类型存储大小值范围精度
float4 字节1.2E-38 到 3.4E+386 位有效位
double8 字节2.3E-308 到 1.7E+30815 位有效位
long double16 字节3.4E-4932 到 1.1E+493219 位有效位

#include <stdio.h>
#include <float.h>
 
int main()
{
   printf("float 存储最大字节数 : %lu \n", sizeof(float)); // 4
   printf("float 最小值: %E\n", FLT_MIN ); // float 最小值: 1.175494E-38
   printf("float 最大值: %E\n", FLT_MAX ); // float 最大值: 3.402823E+38
   printf("精度值: %d\n", FLT_DIG );  // 6
   
   return 0;
}

三、枚举 enum

格式

enum 枚举名 {枚举元素1,枚举元素2,……};

定义

// 方式1、先定义枚举类型,再定义枚举变量
enum DAY
{
	MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;

// 方式2、定义枚举类型的同时定义枚举变量
enum DAY
{
	MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

// 方式3、省略枚举名称,直接定义枚举变量
enum
{
	MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;


调用枚举

int main()
{
    enum DAY day;
    day = WED;
    printf("%d",day);
    return 0;
}

枚举的数值

  • 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。
  • 如果某个量定义了值,后面的在这个量上 +1。
enum season {spring, summer=3, autumn, winter}; // autumn 为 4
  • 在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。

四、void 类型

void 类型指定没有可用的值。它通常用于以下三种情况下:

  1. 函数返回为空
    C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如 void exit (int status);
  2. 函数参数为空
    C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如 int rand(void);
  3. 指针指向 void
    类型为 void * 的指针代表对象的地址,而不是类型。例如,内存分配函数 void *malloc( size_t size ); 返回指向 void 的指针,可以转换为任何数据类型。

五、数组

可以存储一个固定大小的相同类型元素的顺序集合。


内存结构:
所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。

在这里插入图片描述


申明、初始化、访问

  • 需要指定 元素的类型 和 元素的数量(必须大于0,整数常量)
  • 可以逐个初始化数组,也可以使用一个初始化语句
  • 可以通过索引访问,第一个索引值为 0(也被称为基索引)。
double balance[10];
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0}; // {} 的个数,不能大于 [] 指定的数目 
 
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0}; // 如果省略掉了数组的大小,数组的大小则为初始化时 元素的个数。

balance[4] = 50.0; // 把数组中第5个元素的值赋为 50.0

// 访问元素
double salary = balance[9];



#include <stdio.h>
 
int main ()
{
   int n[ 10 ]; /* n 是一个包含 10 个整数的数组 */
   int i,j;
 
   /* 初始化数组元素 */         
   for ( i = 0; i < 10; i++ )
   {
      n[ i ] = i + 100; /* 设置元素 i 为 i + 100 */
   }
   
   /* 输出数组中每个元素的值 */
   for (j = 0; j < 10; j++ )
   {
      printf("Element[%d] = %d\n", j, n[j] );
   }
 
   return 0;
}

多维数组

int threedim[5][10][4];  // 创建三维 5 . 10 . 4 整型数组:

int a[3][4] = {  
 {0, 1, 2, 3} ,   /*  初始化索引号为 0 的行 */
 {4, 5, 6, 7} ,   /*  初始化索引号为 1 的行 */
 {8, 9, 10, 11}   /*  初始化索引号为 2 的行 */
};

// 访问 
int val = a[2][3];

五、字符串

C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。
\0 是用于标记字符串的结束。\0 是转义字符。
空字符(Null character)又称结束符,缩写 NUL,是一个数值为 0 的控制字符。

// 声明和初始化创建了一个 RUNOOB 字符串。由于在数组的末尾存储了空字符 \0,所以字符数组的大小为 7。
char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};

// 以上也可写为:
char site[] = "RUNOOB";
  • 不需要把 null 字符放在字符串常量的末尾。
    C 编译器在初始化数组时,自动把 \0 放在字符串的末尾。

字符串函数

  1. strcpy(s1, s2);, 复制字符串 s2 到字符串 s1。
  2. strcat(s1, s2);, 连接字符串 s2 到字符串 s1 的末尾。
  3. strlen(s1);, 返回字符串 s1 的长度。
  4. strcmp(s1, s2);, 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
  5. strchr(s1, ch);, 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
  6. strstr(s1, s2);, 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

六、结构体


定义

使用 struct 语句定义结构。
格式:

struct tag { 
    member-list
    member-list 
    member-list  
    ...
} variable-list ;

  • tag 是结构体标签。
  • member-list 是标准的变量定义;比如 int i; 或者 float f,或者其他有效的变量定义。
  • variable-list 结构变量,定义在结构的末尾,最后一个分号之前;
    您可以指定一个或多个结构变量。

不同申明方式

申明和枚举类似

// 方式1、同时申明结构体 和 变量
struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

// 方式2、直接申明变量
//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;

// 方式3、先申明结构体;然后定义变量 (我喜欢
//结构体的标签被命名为SIMPLE,没有声明变量
// 和上一个结构类似,但会被编译器当做两个完全不同的类型。
struct SIMPLE
{
    int a;
    char b;
    double c;
};

//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;

// 方式4、使用用typedef创建新类型
typedef struct
{
    int a;
    char b;
    double c; 
} Simple2;

//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;

结构体指向其他结构体或自己
// 结构体的成员 可以包含其他结构体,也可以包含指向 自己结构体类型的指针。
// 通常这种指针的应用 是为了实现一些更高级的数据结构如链表和树等。
// 此结构体的声明包含了其他的结构体
struct COMPLEX
{
    char string[100];
    struct SIMPLE a;
};
 
// 此结构体的声明包含了指向自己类型的指针
struct NODE
{
    char string[100];
    struct NODE *next_node;
};

结构体互相包含
// 两个结构体互相包含,则需要对其中一个结构体进行不完整声明
struct B;    // 对结构体B进行不完整声明
 
//结构体A中包含指向结构体B的指针
struct A
{
    struct B *partner;
    // other members;
};
 
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
    struct A *partner;
    //other members;
};

初始化、访问

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
 
int main()
{
    printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
	
   struct Books Book1;        /* 声明 Book1,类型为 Books */
   struct Books Book2;        /* 声明 Book2,类型为 Books */
 
   /* Book1 详述 */
   strcpy( Book1.title, "C Programming");
   strcpy( Book1.author, "Nuha Ali"); 
   strcpy( Book1.subject, "C Programming Tutorial");
   
   Book1.book_id = 6495407;
 
   /* Book2 详述 */
   strcpy( Book2.title, "Telecom Billing");
   strcpy( Book2.author, "Zara Ali");
   strcpy( Book2.subject, "Telecom Billing Tutorial");
   Book2.book_id = 6495700;
 
   printf( "Book 1 title : %s\n", Book1.title);
   printf( "Book 2 subject : %s\n", Book2.subject);
 
}

结构作为函数参数


/* 函数声明 */
void printBook( struct Books book );

int main( )
{ 
   printBook( Book1 ); 
   printBook( Book2 );
   return 0;
}

void printBook( struct Books book )
{
   printf( "Book title : %s\n", book.title);
   printf( "Book author : %s\n", book.author); 
   printf( "Book book_id : %d\n", book.book_id);
}

指向结构的指针

struct Books *struct_pointer;

// 使用 & 查找结构变量的地址
struct_pointer = &Book1; 

// 使用 -> 运算符 来使用指向该结构的指针 访问结构的成员
struct_pointer->title; 

重写上方函数案例

int main( )
{
   struct Books Book1; 
   struct Books Book2;  
   ...
   printBook( &Book1 );
 
   /* 通过传 Book2 的地址来输出 Book2 信息 */
   printBook( &Book2 );
 
   return 0;
}
void printBook( struct Books *book )
{
   printf( "Book title : %s\n", book->title); 
   printf( "Book book_id : %d\n", book->book_id);
}

七、共用体 union

共用体是一种特殊的数据类型,允许您在 相同的内存位置 存储不同的数据类型。
您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值
共用体提供了一种 使用相同的内存位置 的有效方式。


定义格式

union [union tag]
{
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];

#include <stdio.h>
#include <string.h>
 
union Data
{
   int i;
   float f;
   char  str[20];
};
 
int main( )
{
   union Data data;    
   data.i = 10;
   printf( "data.i : %d\n", data.i); // 10
   
   data.f = 220.5;
   printf( "data.f : %f\n", data.f); // 220.5
   
   strcpy( data.str, "C Programming");
       
   printf( "Memory size occupied by data : %d\n", sizeof(data) ); // 20 
   
   printf( "data.i : %d\n", data.i); // 1917853763
   printf( "data.f : %f\n", data.f); // 4122360580327794860452759994368.000000 
   printf( "data.str : %s\n", data.str);
   
   return 0;
}

  • 共用体占用的内存应足够存储共用体中最大的成员
    在上面的实例中,Data 将占用 20 个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
  • 共用体的 i 和 f 成员的值有损坏,因为 最后赋给变量的值 占用了内存位置。

八、指针

指针也就是内存地址,指针变量 是用来存放 内存地址的变量。
每一个变量 都有一个内存位置,每一个内存位置都可使用 & 运算符 访问地址。


获取变量的地址 &

#include <stdio.h>
 
int main ()
{
    int alice = 10;
    int *p;              // 定义指针变量
    p = &alice;
 
   printf("alice 变量的地址: %p\n", p); // 0x7ffeeaae08d8
   return 0;
}

alice 小朋友住在 编号为 0x7ffeeaae08d8 的房子里。


指针申明

https://www.runoob.com/cprogramming/c-pointers.html


格式

type *var_name;
  • type :指针的基类型
  • 星号是用来指定一个变量是指针

int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

使用 * 获取指针指向变量的值

#include <stdio.h>
 
int main ()
{
   int  var = 20;   /* 实际变量的声明 */
   int  *ip;        /* 指针变量的声明 */
 
   ip = &var;  /* 在指针变量中存储 var 的地址 */
 
   printf("var 变量的地址: %p\n", &var  ); 
   printf("ip 变量存储的地址: %p\n", ip ); 
   printf("*ip 变量的值: %d\n", *ip );
 
   return 0;
}

NULL 指针

NULL 指针是一个 定义在标准库中的 值为零 的常量。
如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。
赋为 NULL 值的指针,被称为空指针。

#include <stdio.h>
 
int main ()
{
   int  *ptr = NULL; 
   printf("ptr 的地址是 %p\n", ptr  );  // 0x0
   return 0;
}

常见用法:
在大多数的操作系统上,程序不允许访问地址为 0 的内存,因为该内存是操作系统保留的。
然而,内存地址 0 有特别重要的意义,它表明 该指针 不指向一个可访问的内存位置。

但按照惯例,如果指针包含空值(零值),则假定它不指向任何东西。
如需检查一个空指针,您可以使用 if 语句,如下所示:

if(ptr)     /* 如果 p 非空,则完成 */
if(!ptr)    /* 如果 p 为空,则完成 */

九、函数

定义函数

格式:

return_type function_name( parameter list )
{
   body of the function
}

  • 在函数声明中,参数的名称 并不重要
  • 只有参数的类型是必需的,
  • 因此下面也是有效的声明:
int max(int, int);
  • 当您在源文件A中定义函数f1,另文件B中调用 f1 时,函数声明是必需的。
    这种情况下,您应该在文件B 顶部声明函数。

#include <stdio.h>
 
/* 函数声明 */
int max(int num1, int num2);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
   int ret;
  
   ret = max(a, b); 
   printf( "Max value is : %d\n", ret );
 
   return 0;
}
 
/* 函数返回两个数中较大的那个数 */
int max(int num1, int num2) 
{
   /* 局部变量声明 */
   int result;
 
   if (num1 > num2)
      result = num1;
   else
      result = num2;
 
   return result; 
}

引用调用

形参为 指向实参地址的指针。
当对形参的指向操作时,就相当于对实参本身进行的操作。

#include <stdio.h>
 
/* 函数声明 */
void swap(int *x, int *y);
 
int main ()
{
   /* 局部变量定义 */
   int a = 100;
   int b = 200;
 
   printf("交换前,a 的值: %d\n", a );
   printf("交换前,b 的值: %d\n", b );
 
   /* 调用函数来交换值
    * &a 表示指向 a 的指针,即变量 a 的地址 
    * &b 表示指向 b 的指针,即变量 b 的地址 
   */
   swap(&a, &b);
 
   printf("交换后,a 的值: %d\n", a );
   printf("交换后,b 的值: %d\n", b );
 
   return 0;
}

/* 函数定义 */
void swap(int *x, int *y)
{
   int temp;
   temp = *x;    /* 保存地址 x 的值 */
   *x = *y;      /* 把 y 赋值给 x */
   *y = temp;    /* 把 temp 赋值给 y */
  
   return;
}

typedef 类型别名

格式:

// 定义了一个术语 BYTE,  可作为类型 unsigned char 的缩写
typedef unsigned char BYTE;

// 定义变量
BYTE  b1, b2;

  • 按照惯例,定义时会大写字母,以便提醒用户 类型名称 是一个象征性的缩写。
  • 但也可以使用小写字母。

typedef vs #define

#define 是 C 指令,用于为各种数据类型定义别名
与 typedef 类似,但是它们有以下几点不同:

  • typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
  • typedef 是由 编译器 执行解释的,#define 语句是由 预编译器 进行处理的。

常量

整数常量

进制前缀:

  • 十六进制:0x 或 0X
  • 八进制:0
  • 十进制:默认不带前缀

数字常量后缀
整数常量也可以带一个后缀,是 U 和 L 的组合,可大写可小写,顺序任意。

  • U 表示无符号整数(unsigned)
  • L 表示长整数(long)

212         /* 合法的 */
215u        /* 合法的 */
0xFeeL      /* 合法的 */
078         /* 非法的:8 不是八进制的数字 */
032UU       /* 非法的:不能重复后缀 */
85         /* 十进制 */
0213       /* 八进制 */
0x4b       /* 十六进制 */
30         /* 整数 */
30u        /* 无符号整数 */
30l        /* 长整数 */
30ul       /* 无符号长整数 */

浮点常量

浮点常量由整数部分、小数点、小数部分和指数部分组成。
当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。

3.14159       /* 合法的 */
314159E-5L    /* 合法的 */
510E          /* 非法的:不完整的指数 */
210f          /* 非法的:没有小数或指数 */
.e55          /* 非法的:缺少整数或分数 */

定义常量

两种方式:

  1. 使用 #define 预处理器。
  2. 使用 const 关键字。

格式如下:

#define identifier value

const type variable = value;  // 申明和赋值必须写在同一行

示例:

#include <stdio.h>
 
#define LENGTH 10   
#define WIDTH  5
#define NEWLINE '\n'
 
int main()
{
   int area;   
   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);
 		
   const int  LENGTH = 10;
   const int  WIDTH  = 5;
   const char NEWLINE = '\n';
   
   int area;  
   
   area = LENGTH * WIDTH;
   printf("value of area : %d", area);
   printf("%c", NEWLINE);
   
   return 0;
}

存储类

存储类 定义 C 程序中 变量/函数 的范围(可见性)和生命周期。
这些说明符 放置在它们所修饰的类型之前。

常见存储类:

  • auto
  • register
  • static
  • extern

1、auto 类

// auto 存储类 是所有 局部变量 默认的存储类。
int mount;
auto int month; // 于上方等价

2、register 存储类

register 存储类用于定义 存储在寄存器中 而不是 RAM 中的局部变量。
这意味着 变量的最大尺寸 等于 寄存器的大小(通常是一个字),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

register int  miles; 
  • 寄存器只用于需要快速访问的变量,比如计数器。
  • 定义 ‘register’ 并不意味着 变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。

3、static 存储类

#include <stdio.h>
 
/* 函数声明 */
void func1(void);
 
static int count=10; // 全局变量 - static 是默认的  
 
int main()
{
  while (count--) {
      func1();
  }
  return 0;
}
 
void func1(void)
{
 // 'thingy' 是 'func1' 的局部变量 - 只初始化一次
 // 每次调用函数 'func1' 'thingy' 值不会被重置。               
  static int thingy=5;
  thingy++;
  printf(" thingy 为 %d , count 为 %d\n", thingy, count);
}
  • static 存储类指示 编译器在程序的生命周期内 保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。
    因此,使用 static 修饰局部变量可以在函数调用之间 保持局部变量的值。
  • static 修饰符也可以应用于全局变量。
  • 当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
  • 全局声明的一个 static 变量或方法,可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。

4、extern 存储类

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。
通常用于当有两个或多个文件共享相同的全局变量或函数的时候。


文件1: main.c

#include <stdio.h>
 
int count ;
extern void write_extern();
 
int main()
{
   count = 5;
   write_extern();
}

文件2: support.c

#include <stdio.h>
 
extern int count; // 引用文件1中的 count 变量 
 
void write_extern(void)
{
   printf("count is %d\n", count);
}

编译

gcc main.c support.c

生成 a.out 文件,执行结果为 count is 5


  • 当使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
  • 当文件A定义了一个 全局变量或者函数,其他文件想要引用,在自己的文件中使用 extern 来修饰。

运算符

  • 算术运算符:+,-,*, /, %, ++,–
  • 关系运算符:==,!=, >, <, >=, <=
  • 逻辑运算符:&&, ||, !
  • 位运算符 :&, |, ^, ~,<<, >>
  • 赋值运算符:=,+=,-=,*=, /=, %=, <<=, >>=, &=, ^=, |=
  • 杂项运算符:sizeof(),&, *? :

打印

参考:http://c.biancheng.net/view/159.html


输出控制符

控制符说明
%d按十进制整型数据的实际长度输出。
%ld输出长整型数据。
%mdm 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以空格,若大于 m,则按实际位数输出。
%u输出无符号整型(unsigned)。输出无符号整型时也可以用 %d,这时是将无符号转换成有符号数,然后输出。但编程的时候最好不要这么写,因为这样要进行一次转换,使 CPU 多做一次无用功。
%c用来输出一个字符。
%f用来输出实数,包括单精度和双精度,以小数形式输出。不指定字段宽度,由系统自动指定,整数部分全部输出,小数部分输出 6 位,超过 6 位的四舍五入。
%.mf输出实数时小数点后保留 m 位,注意 m 前面有个点。
%o以八进制整数形式输出,这个就用得很少了,了解一下就行了。
%s用来输出字符串。用 %s 输出字符串同前面直接输出字符串是一样的。但是此时要先定义字符数组或字符指针存储或指向字符串,这个稍后再讲。
%x(或 %X 或 %#x 或 %#X)以十六进制形式输出整数,这个很重要。


2022-02-25(五)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AI工程仔

请我喝杯伯爵奶茶~!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值