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 的指针,可以转换为任何数据类型。



五、数组

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


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

在这里插入图片描述


1、申明、初始化、访问

  • 需要指定 元素的类型 和 元素的数量(必须大于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 结构变量,定义在结构的末尾,最后一个分号之前,您可以指定一个或多个结构变量。

不同申明方式

申明和枚举类似

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

//这个结构体并没有标明其标签
struct 
{
    int a;
    char b;
    double c;
} s1;

//结构体的标签被命名为SIMPLE,没有声明变量
// 和上一个结构类似,但会被编译器当做两个完全不同的类型。
struct SIMPLE
{
    int a;
    char b;
    double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;

//也可以用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(五)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:技术黑板 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值