文章目录
改编自 https://www.runoob.com/cprogramming/c-data-types.html
类型概览
C 中的类型可分为以下几种:
- 基本类型:整数类型和浮点类型。
- 枚举类型
- void 类型
- 派生类型:指针类型、数组类型、结构类型、共用体类型 和 函数类型(函数返回值的类型)。
数组类型和结构类型统称为聚合类型
类型的大小
变量的类型决定了变量存储占用的空间,以及如何解释存储的位模式。
各种类型的存储大小与系统位数有关,您可以使用 sizeof
运算符得到准确的大小。
位、字节、字
位是最小的存储单位,每一个位存储一个1位的二进制码
一个字节由8个位组成。如 01001011
字通常为16、32或64个位组成;即2个字节,4、8个字节。
一、整数类型
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
#include <stdio.h>
#include <limits.h>
int main()
{
printf("int 存储大小 : %lu \n", sizeof(int));
return 0;
}
二、浮点类型
标准浮点类型的存储大小、值范围和精度的细节:
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位有效位 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位有效位 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位有效位 |
#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 类型指定没有可用的值。它通常用于以下三种情况下:
- 函数返回为空
C 中有各种函数都不返回值,或者您可以说它们返回空。不返回值的函数的返回类型为空。例如void exit (int status);
- 函数参数为空
C 中有各种函数不接受任何参数。不带参数的函数可以接受一个 void。例如int rand(void);
- 指针指向 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
放在字符串的末尾。
字符串函数
strcpy(s1, s2);
, 复制字符串 s2 到字符串 s1。strcat(s1, s2);
, 连接字符串 s2 到字符串 s1 的末尾。strlen(s1);
, 返回字符串 s1 的长度。strcmp(s1, s2);
, 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。strchr(s1, ch);
, 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。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 /* 非法的:缺少整数或分数 */
定义常量
两种方式:
- 使用
#define
预处理器。 - 使用
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 | 输出长整型数据。 |
%md | m 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以空格,若大于 m,则按实际位数输出。 |
%u | 输出无符号整型(unsigned)。输出无符号整型时也可以用 %d,这时是将无符号转换成有符号数,然后输出。但编程的时候最好不要这么写,因为这样要进行一次转换,使 CPU 多做一次无用功。 |
%c | 用来输出一个字符。 |
%f | 用来输出实数,包括单精度和双精度,以小数形式输出。不指定字段宽度,由系统自动指定,整数部分全部输出,小数部分输出 6 位,超过 6 位的四舍五入。 |
%.mf | 输出实数时小数点后保留 m 位,注意 m 前面有个点。 |
%o | 以八进制整数形式输出,这个就用得很少了,了解一下就行了。 |
%s | 用来输出字符串。用 %s 输出字符串同前面直接输出字符串是一样的。但是此时要先定义字符数组或字符指针存储或指向字符串,这个稍后再讲。 |
%x(或 %X 或 %#x 或 %#X) | 以十六进制形式输出整数,这个很重要。 |
2022-02-25(五)