import{_ as s,c as i,o as a,a6 as n}from"./chunks/framework.CZKtKhAm.js";const l="/c/assets/1.L8V3GBrc.png",p="/c/assets/2.BkF1yWLn.png",t="/c/assets/3.CceY8r_n.png",h="/c/assets/4.DHaUgOwj.png",k="/c/assets/5.CXGaq_zh.png",e="/c/assets/6.BrZYAEEl.png",r="/c/assets/7.DpCroFHv.png",d="/c/assets/8.Cr7P0Gji.png",E="/c/assets/9.NlBmD7pA.png",c="/c/assets/10.ZiBq6Pno.png",g="/c/assets/11.DAgmsf-w.png",F="/c/assets/12.B2iC37fw.png",y="/c/assets/14.BlE3ZFud.png",u="/c/assets/15.CdvhiwcU.png",b="/c/assets/16.D74t3-Xt.png",o="/c/assets/17.DdAZIfeP.png",C="/c/assets/18.B4Zfj2jX.png",m="/c/assets/19.IfpA0D5n.png",B="/c/assets/20.CfIzYazX.png",A="/c/assets/21.eHnZcbpI.png",D="/c/assets/22.BNoKlM4o.png",v="/c/assets/23.CMVtwrqr.png",q="/c/assets/24.CN19KJG7.png",f="/c/assets/25.BIhiJHjz.png",_="/c/assets/26.A_E4n4g8.png",x="/c/assets/27.Bv4qFbwF.png",z="/c/assets/28.IKaazo2J.png",w="/c/assets/29.CGTVELeO.png",I="/c/assets/30.Bz4_lEH0.gif",P="/c/assets/31.CcDWE4nn.png",T="/c/assets/32.B7y2_JVX.gif",N="/c/assets/33.Btcc3rs2.gif",L="/c/assets/34.CR4ARW8y.png",O="/c/assets/33.Btcc3rs2.gif",S="/c/assets/36.DOX3ymYP.gif",U="/c/assets/37.Cie9_tkP.gif",M="/c/assets/38.DLjH9Ges.png",$=JSON.parse('{"title":"第一章:数据类型(⭐)","description":"","frontmatter":{},"headers":[],"relativePath":"notes/01_c-basic/03_xdx/index.md","filePath":"notes/01_c-basic/03_xdx/index.md","lastUpdated":1721697894000}'),X={name:"notes/01_c-basic/03_xdx/index.md"},R=n('
根据变量
中存储
的值
的不同
,我们可以将变量
分为两类:
普通变量
:变量所对应的内存中存储的是普通值
。指针变量
:变量所对应的内存中存储的是另一个变量的地址
。如下图所示:
NOTE
普通变量和指针变量的相同点:
普通变量和指针变量的不同点:
NOTE
普通变量
中存储
的值
的类型不同,可以将普通变量类型
划分为基本数据类型
(整型、字符类型、浮点类型、布尔类型)和复合数据类型
(数组类型、结构体类型、共用体类型、枚举类型)。指针变量
所指向空间
中存储
的值
的类型不同,可以将指针类型
分为基本数据类型指针
、复合数据类型指针
、函数指针
、数组指针
等,例如:如果指针所指向的空间保存的是 int 类型,那么该指针就是 int 类型的指针。内存空间
大小的不同,可以将整数类型划分为:类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned short (无符号短整型) | 2 字节 | 0 ~ 65,535 (2^16 - 1) |
[signed] short(有符号短整型,默认) | 2 字节 | -32,768 (- 2^15) ~ 32,767 (2^15 -1) |
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned int(无符号整型) | 4 字节(通常) | 0 ~ 4294967295 (0 ~2^32 -1) |
[signed] int(有符号整型,默认) | 4 字节(通常) | -2147483648(- 2^31) ~ 2147483647 (2^31-1) |
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned long(无符号长整型) | 4 字节(通常) | 0 ~2^32 -1 |
[signed] long(有符号长整型,默认) | 4 字节(通常) | - 2^31 ~ 2^31-1 |
类型 | 存储空间(内存空间) | 取值范围 |
---|---|---|
unsigned long long(无符号长整型) | 8 字节(通常) | 0 ~2^64 -1 |
[signed] long long(有符号长整型,默认) | 8 字节(通常) | - 2^63 ~ 2^63-1 |
NOTE
sizeof(short int) ≤ sizeof(int) ≤ sizeof(long int) ≤ sizeof(long long)
,具体的存储空间由编译系统自行决定;其中,sizeof 是测量类型或变量、常量长度的运算符。最常用的整数类型
就是 int
类型了,如果取值范围不够,就使用 long 或 long long 。格式占位符
非常多,只需要大致了解即可;因为,我们在实际开发中,一般都会使用 C++ 或 Rust 以及其它的高级编程语言,如:Java 等,早已经解决了需要通过格式占位符
来输入和输出变量。unsigned short x = 10 ; // 无符号短整型
short x = -10; // 有符号短整型
NOTE
printf
中无符号短整型(unsigned short)
的格式占位符
是 %hu
,有符号短整型(signed short)
的格式占位符
是 %hd
。sizeof
运算符获取无符号短整型(unsigned short)
和 有符号短整型(signed short)
的存储空间(所占内存空间)
。#include <limits.h>
来获取 无符号短整型(unsigned short)
和有符号短整型(signed short)
的取值范围
。#include <stdio.h>
int main() {
// 定义有符号 short 类型
signed short s1 = -100;
printf("s1 = %hd \\n", s1); // s1 = -100
// 定义无符号 short 类型
unsigned short s2 = 100;
printf("s2 = %hu \\n", s2); // s2 = 100
// 定义 short 类型,默认是有符号
short s3 = -200;
printf("s3 = %hd \\n", s3); // s3 = -200
return 0;
}
#include <stdio.h>
int main() {
size_t s1 = sizeof(unsigned short);
printf("unsigned short 的存储空间是 %zu 字节 \\n", s1); // 2
size_t s2 = sizeof(signed short);
printf("signed short 的存储空间是 %zu 字节 \\n", s2); // 2
size_t s3 = sizeof(short);
printf("short 的存储空间是 %zu 字节 \\n", s3); // 2
return 0;
}
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned short 类型的范围是[0,%hu]\\n", USHRT_MAX); // [0,65535]
printf("short 类型的范围是[%hd,%hd]\\n", SHRT_MIN,SHRT_MAX); // [-32768,32767]
return 0;
}
unsigned int x = 10 ; // 无符号整型
int x = -10; // 有符号整型
NOTE
printf
中无符号整型(unsigned int)
的格式占位符
是 %u
,有符号整型(signed int)
的格式占位符
是 %d
。sizeof
运算符获取无符号整型(unsigned int)
和 有符号整型(signed int)
的存储空间(所占内存空间)
。#include <limits.h>
来获取 无符号整型(unsigned int)
和有符号整型(signed int)
的取值范围
。#include <stdio.h>
int main() {
// 定义有符号 int 类型
signed int i1 = -100;
printf("i1 = %d \\n", i1); // i1 = -100
// 定义无符号 int 类型
unsigned int i2 = 100;
printf("i2 = %u \\n", i2); // i2 = 100
// 定义 int 类型,默认是有符号
short i3 = -200;
printf("i3 = %d \\n", i3); // i3 = -200
return 0;
}
#include <stdio.h>
int main() {
size_t i1 = sizeof(unsigned int);
printf("unsigned int 的存储空间是 %zu 字节 \\n", i1); // 4
size_t i2 = sizeof(signed int);
printf("signed int 的存储空间是 %zu 字节 \\n", i2); // 4
size_t i3 = sizeof(int);
printf("int 的存储空间是 %zu 字节 \\n", i3); // 4
return 0;
}
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned int 类型的范围是[0,%u]\\n", UINT_MAX); // [0,4294967295]
printf("int 类型的范围是[%d,%d]\\n", INT_MIN,INT_MAX); // [-2147483648,2147483647]
return 0;
}
unsigned long x = 10 ; // 无符号长整型
long x = -10; // 有符号长整型
NOTE
printf
中无符号长整型(unsigned long)
的格式占位符
是 %lu
,有符号长整型(signed long)
的格式占位符
是 %ld
。sizeof
运算符获取无符号长整型(unsigned long)
和 有符号长整型(signed long)
的存储空间(所占内存空间)
。#include <limits.h>
来获取 无符号长整型(unsigned long)
和有符号长整型(signed long)
的取值范围
。#include <stdio.h>
int main() {
// 定义有符号 long 类型
signed long l1 = -100;
printf("l1 = %ld \\n", l1); // l1 = -100
// 定义无符号 long 类型
unsigned long l2 = 100;
printf("l2 = %lu \\n", l2); // l2 = 100
// 定义 long 类型,默认是有符号
long l3 = -200;
printf("l3 = %ld \\n", l3); // l3 = -200
return 0;
}
#include <stdio.h>
int main() {
size_t l1 = sizeof(unsigned long);
printf("unsigned long 的存储空间是 %zu 字节 \\n", l1); // 4
size_t l2 = sizeof(signed long);
printf("signed long 的存储空间是 %zu 字节 \\n", l2); // 4
size_t l3 = sizeof(long);
printf("long 的存储空间是 %zu 字节 \\n", l3); // 4
return 0;
}
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned long 类型的范围是[0,%lu]\\n", ULONG_MAX); // [0,4294967295]
printf("long 类型的范围是[%ld,%ld]\\n", LONG_MIN,LONG_MAX); // [-2147483648,2147483647]
return 0;
}
unsigned long long x = 10 ; // 无符号长长整型
long long x = -10; // 有符号长长整型
NOTE
printf
中无符号长长整型(unsigned long long)
的格式占位符
是 %llu
,有符号长长整型(signed long long)
的格式占位符
是 %lld
。sizeof
运算符获取无符号长长整型(unsigned long long)
和 有符号长长整型(signed long long)
的存储空间(所占内存空间)
。#include <limits.h>
来获取 无符号长长整型(unsigned long long)
和有符号长长整型(signed long long)
的取值范围
。#include <stdio.h>
int main() {
// 定义有符号 long long 类型
signed long long ll1 = -100;
printf("ll1 = %lld \\n", ll1); // ll1 = -100
// 定义无符号 long long 类型
unsigned long long ll2 = 100;
printf("ll2 = %llu \\n", ll2); // ll2 = 100
// 定义 long long 类型,默认是有符号
long long ll3 = -200;
printf("ll3 = %lld \\n", ll3); // ll3 = -200
return 0;
}
#include <stdio.h>
int main() {
size_t ll1 = sizeof(unsigned long long);
printf("unsigned long long 的存储空间是 %zu 字节 \\n", ll1); // 8
size_t ll2 = sizeof(signed long long);
printf("signed long long 的存储空间是 %zu 字节 \\n", ll2); // 8
size_t ll3 = sizeof(long long);
printf("long long 的存储空间是 %zu 字节 \\n", ll3); // 8
return 0;
}
#include <limits.h>
#include <stdio.h>
int main() {
printf("unsigned long long 类型的范围是[0,%llu]\\n", ULLONG_MAX); // [0,18446744073709551615]
printf("long long 类型的范围是[%lld,%lld]\\n", LLONG_MIN,LLONG_MAX); // [-9223372036854775808,9223372036854775807]
return 0;
}
字面量
是源代码
中一个固定值
的表示方法
,用于直接表示数据,即:int num1 = 100; // 100 就是字面量
long num2 = 100L; // 100L 就是字面量
long long num3 = 100LL; // 100LL 就是字面量
NOTE
#include <stdio.h>
int main() {
int num = 100;
printf("num = %d\\n", num); // num = 100
long num2 = 100L;
printf("num2 = %ld\\n", num2); // num2 = 100
long long num3 = 100LL;
printf("num3 = %lld\\n", num3); // num3 = 100
unsigned int num4 = 100U;
printf("num4 = %u\\n", num4); // num4 = 100
unsigned long num5 = 100LU;
printf("num5 = %lu\\n", num5); // num5 = 100
unsigned long long num6 = 100ULL;
printf("num6 = %llu\\n", num6); // num6 = 100
return 0;
}
NOTE
<stdint.h>
中定义了一些新的类型别名,如下所示:类型名称 | 含义 |
---|---|
int8_t | 8 位有符号整数 |
int16_t | 16 位有符号整数 |
int32_t | 32 位有符号整数 |
int64_t | 64 位有符号整数 |
uint8_t | 8 位无符号整数 |
uint16_t | 16 位无符号整数 |
uint32_t | 32 位无符号整数 |
uint64_t | 64 位无符号整数 |
NOTE
上面的这些类型都是类型别名,编译器会指定它们指向的底层类型,如:在某个系统中,如果 int 类型是 32 位,那么 int32_t 就会指向 int ;如果 long 类型是 32 位,那么 int32_t 就会指向 long。
#include <stdio.h>
#include <stdint.h>
int main() {
// 变量 x32 声明为 int32_t 类型,可以保证是 32 位(4个字节)的宽度。
int32_t x32 = 45933945;
printf("x32 = %d \\n", x32); // x32 = 45933945
return 0;
}
sizeof(表达式)
NOTE
sizeof(...)
的返回值类型
是 size_t
。printf
中使用占位符 %zu
来处理 size_t
类型的值。#include <stdio.h>
#include <stddef.h>
int main() {
size_t s = sizeof(int);
printf("%zu \\n", s); // 4
return 0;
}
#include <stdio.h>
#include <stddef.h>
int main() {
int num = 10;
size_t s = sizeof(num);
printf("%zu \\n", s); // 4
return 0;
}
#include <stdio.h>
#include <stddef.h>
int main() {
size_t s = sizeof(10);
printf("%zu \\n", s); // 4
return 0;
}
所谓的数值溢出指的是:当超过一个数据类型能够存放的最大范围的时候,数值就会溢出。
在 C 语言中,整数
的数据类型
分为无符号
和有符号
的,其在底层表示和存储是不一样的,即:
无符号整数不使用最高位作为符号位
,所有的位都用于表示数值,如:对于一个 4 位无符号整数,二进制表示的范围是从 0000 到 1111 ,那么十进制表示的范围是从 0 到 15。有符号整数使用最高位作为符号位
,这意味着它们可以表示正数和负数,通常使用补码来表示有符号整数。在补码表示法中:最高位为 0 表示正数、最高位为 1 表示负数,如:对于一个4位有符号整数,二进制表示的范围是从 0000(0) 到 0111 (7),1000 (-8)到 1111(-1)。NOTE
对于无符号的数值溢出:
那么,无符号的上溢出,原理就是这样的:
对于有符号的数值溢出:
那么,有符号的上溢出,原理就是这样的:
NOTE
在实际开发中,选择合适的数据类型,以避免数值溢出问题!!!
#include <limits.h>
#include <stdio.h>
int main() {
unsigned short s1 = USHRT_MAX + 1;
printf("无符号的上溢出 = %hu \\n", s1); // 0
unsigned short s2 = 0 - 1;
printf("无符号的下溢出 = %hu \\n", s2); // 65535
return 0;
}
#include <limits.h>
#include <stdio.h>
int main() {
short s1 = SHRT_MAX + 1;
printf("有符号的上溢出 = %hd \\n", s1); // -32768
short s2 = SHRT_MIN - 1;
printf("有符号的下溢出 = %hd \\n", s2); // 32767
return 0;
}
整数
,如:18、25 之外,还会使用到小数
,如:3.1415926、6.18 等,小数
在计算机中也被称为浮点数
(和底层存储有关)。整数
在计算机底层的存储被称为定点存储
,如下所示:小数
在计算机底层的存储被称为浮点存储
,如下所示:NOTE
类型 | 存储大小 | 值的范围 | 有效小数位数 |
---|---|---|---|
float(单精度) | 4 字节 | 1.2E-38 ~ 3.4E+38 | 6 ~ 9 |
double(双精度) | 8 字节 | 2.3E-308 ~ 1.7E+308 | 15 ~ 18 |
long double(长双精度) | 16 字节 | 3.4E-4932 ~ 1.2E+4932 | 18 或更多 |
NOTE
对于 float 类型的格式占位符,是 %f
,默认会保留 6
位小数;可以指定小数位,如:%.2f
表示保留 2
位小数。
对于 double 类型的格式占位符,是 %lf
,默认会保留 6
位小数;可以指定小数位,如:%.2lf
表示保留 2
位小数。
对于 long double 类型的格式占位符,是 %Lf
,默认会保留 6
位小数;可以指定小数位,如:%.2Lf
表示保留 2
位小数。
如果想输出科学计数法
形式的浮点数,则使用 %e
。
示例:
#include <stdio.h>
int main() {
float f1 = 10.0;
printf("f1 = %f \\n", f1); // f1 = 10.000000
printf("f1 = %.2f \\n", f1); // f1 = 10.00
return 0;
}
#include <stdio.h>
int main() {
double d1 = 13.14159265354;
printf("d1 = %lf \\n", d1); // d1 = 13.141593
printf("d1 = %.2lf \\n", d1); // d1 = 13.14
return 0;
}
#include <stdio.h>
int main() {
long double d1 = 13.14159265354;
printf("d1 = %LF \\n", d1); // d1 = 13.141593
printf("d1 = %.2LF \\n", d1); // d1 = 13.14
return 0;
}
#include <stdio.h>
int main() {
float f1 = 3.1415926;
double d2 = 3.14e2;
printf("f1 = %.2f \\n", f1); // f1 = 3.14
printf("f1 = %.2e \\n", f1); // f1 = 3.14e+00
printf("d2 = %.2lf \\n", d2); // d2 = 314.00
printf("d2 = %.2e \\n", d2); // d2 = 3.14e+02
return 0;
}
浮点数字面量默认是 double 类型。
如果需要表示 float 类型的字面量,需要后面添加后缀 f 或 F。
如果需要表示 long double 类型的字面量,需要后面添加后缀 l 或 L。
示例:
#include <stdio.h>
int main() {
float f1 = 3.1415926f;
double d2 = 3.1415926;
long double d3 = 3.1415926L;
printf("f1 = %.2f \\n", f1); // f1 = 3.14
printf("d2 = %.3lf \\n", d2); // d2 = 3.142
printf("d3 = %.4Lf \\n", d3); // d3 = 3.1416
return 0;
}
可以通过 sizeof 运算符来获取 float、double 以及 long double 类型占用的内存大小(存储空间)。
示例:
#include <stdio.h>
int main() {
printf("float 的存储空间是 %zu 字节 \\n", sizeof(float)); // 4
printf("double 的存储空间是 %zu 字节 \\n", sizeof(double)); // 8
printf("long double 的存储空间是 %zu 字节 \\n", sizeof(long double)); // 16
return 0;
}
可以通过 #include <float.h>
来获取类型的取值范围。
示例:
#include <float.h>
#include <stdio.h>
int main() {
printf("float 的取值范围是:[%.38f, %f] \\n", FLT_MIN, FLT_MAX);
printf("double 的取值范围是:[%lf, %lf] \\n", DBL_MIN, DBL_MAX);
printf("double 的取值范围是:[%Lf, %Lf] \\n", LDBL_MIN, LDBL_MAX);
return 0;
}
好
,我的性别是 女
,我今年 10
岁,像这类数据,在 C 语言中就可以用字符(char)来表示。字符类型
可以表示单
个字符,如:'1'
、'A'
、'&'
。NOTE
转义字符 \\
来表示特殊含义的字符。转义字符 | 说明 |
---|---|
\\b | 退格 |
\\n | 换行符 |
\\r | 回车符 |
\\t | 制表符 |
\\" | 双引号 |
\\' | 单引号 |
\\\\ | 反斜杠 |
... |
在 C 语言中,使用 %c
来表示 char 类型。
示例:
#include <stdio.h>
int main() {
char c = '&';
printf("c = %c \\n", c); // c = &
char c2 = 'a';
printf("c2 = %c \\n", c2); // c2 = a
char c3 = 'A';
printf("c3 = %c \\n", c3); // c3 = A
return 0;
}
可以通过 sizeof 运算符来获取 char 类型占用的内存大小(存储空间)。
示例:
#include <stdio.h>
int main() {
printf("char 的存储空间是 %d 字节\\n", sizeof(char)); // 1
printf("unsigned char 的存储空间是 %d 字节\\n", sizeof(unsigned char)); // 1
return 0;
}
可以通过 #include <limits.h>
来获取类型的取值范围。
示例:
#include <limits.h>
#include <stdio.h>
int main() {
printf("char 范围是[%d,%d] \\n", CHAR_MIN,CHAR_MAX); // [-128,127]
printf("unsigned char 范围是[0,%d]\\n", UCHAR_MAX); // [0,255]
return 0;
}
字符类型的数据
在计算机中存储
和读取
的过程,如下所示:#include <limits.h>
#include <stdio.h>
int main() {
// char 类型字面量需要使用单引号包裹
char a1 = 'A';
char a2 = '9';
char a3 = '\\t';
printf("c1=%c, c3=%c, c2=%c \\n", a1, a3, a2);
// char 类型本质上整数可以进行运算
char b1 = 'b';
char b2 = 101;
printf("%c->%d \\n", b1, b1);
printf("%c->%d \\n", b2, b2);
printf("%c+%c=%d \\n", b1, b2, b1 + b2);
// char 类型取值范围
unsigned char c1 = 200; // 无符号 char 取值范围 0 ~255
signed char c2 = 200; // 有符号 char 取值范围 -128~127,c2会超出范围
char c3 = 200; // 当前系统,char 默认是 signed char
printf("c1=%d, c2=%d, c3=%d", c1, c2, c3);
return 0;
}
在 C 语言标准(C89)中,并没有为布尔值单独设置一个数据类型,所以在判断真、假的时候,使用 0
表示 false
(假),非 0
表示 true
(真)。
示例:
#include <stdio.h>
int main() {
// 使用整型来表示真和假两种状态
int handsome = 0;
printf("帅不帅[0 丑,1 帅]: ");
scanf("%d", &handsome);
if (handsome) {
printf("你真的很帅!!!");
} else {
printf("你真的很丑!!!");
}
return 0;
}
判断真假的时候,以 0
为 false
(假)、1
为 true
(真),并不直观;所以,我们可以借助 C 语言的宏定义。
示例:
#include <stdio.h>
// 宏定义
#define BOOL int
#define TRUE 1
#define FALSE 0
int main() {
BOOL handsome = 0;
printf("帅不帅[FALSE 丑,TRUE 帅]: ");
scanf("%d", &handsome);
if (handsome) {
printf("你真的很帅!!!");
} else {
printf("你真的很丑!!!");
}
return 0;
}
在 C99 中提供了 _Bool
关键字,用于表示布尔类型;其实,_Bool
类型的值是整数类型的别名,和一般整型不同的是,_Bool
类型的值只能赋值为 0
或 1
(0 表示假、1 表示真),其它非 0
的值都会被存储为 1
。
示例:
#include <stdio.h>
int main() {
_Bool handsome = 0;
printf("帅不帅[0 丑,1 帅]: ");
scanf("%d", &handsome);
if (handsome) {
printf("你真的很帅!!!");
} else {
printf("你真的很丑!!!");
}
return 0;
}
<stdbool.h>
,定义了 bool
代表 _Bool
,false
代表 0
,true
代表 1
。NOTE
在 C++、Java 等高级编程语言中是有 boolean 类型的关键字的。
#include <stdio.h>
int main() {
bool handsome = false;
printf("帅不帅[false 丑,true 帅]: ");
scanf("%d", &handsome);
if (handsome) {
printf("你真的很帅!!!");
} else {
printf("你真的很丑!!!");
}
return 0;
}
窄类型会自动转换为宽类型
,这样就不会造成精度损失。NOTE
最好避免无符号整数与有符号整数的混合运算,因为这时 C 语言会自动将 signed int 转为 unsigned int ,可能不会得到预期的结果。
#include <stdio.h>
/**
* 不同的整数类型混合运算时,宽度较小的类型会提升为宽度较大的类型,比如 short 转为 int ,int 转为 long 等。
*/
int main() {
short s1 = 10;
int i = 20;
// s1 是 short 类型,i 是 int 类型,当 s1 和 i 运算的时候,会自动转为 int 类型后,然后再计算。
int result = s1 + i;
printf("result = %d \\n", result);
return 0;
}
#include <stdio.h>
int main() {
int n2 = -100;
unsigned int n3 = 20;
// n2 是有符号,n3 是无符号,当 n2 和 n3 运算的时候,会自动转为无符号类型后,然后再计算。
int result = n2 + n3;
printf("result = %d \\n", result);
return 0;
}
#include <stdio.h>
/**
* 不同的浮点数类型混合运算时,宽度较小的类型转为宽度较大的类型,比如 float 转为 double ,double 转为 long double 。
*/
int main() {
float f1 = 1.25f;
double d2 = 4.58667435;
// f1 是 float 类型,d2 是 double 类型,当 f1 和 d2 运算的时候,会自动转为 double 类型后,然后再计算。
double result = f1 + d2;
printf("result = %.8lf \\n", result);
return 0;
}
#include <stdio.h>
/**
* 整型与浮点型运算,整型转为浮点型
*/
int main() {
int n4 = 10;
double d3 = 1.67;
// n4 是 int 类型,d3 是 double 类型,当 n4 和 d3 运算的时候,会自动转为 double 类型后,然后再计算。
double result = n4 + d3;
printf("%.2lf", result);
return 0;
}
NOTE
C 语言在检查类型匹配方面不太严格,最好不要养成这样的习惯。
#include <stdio.h>
int main() {
// 赋值:窄类型赋值给宽类型
int a1 = 10;
double a2 = a1;
printf("a2: %.2f\\n", a2); // a2: 10.00
// 转换:将宽类型转换为窄类型
double b1 = 10.5;
int b2 = b1;
printf("b2: %d\\n", b2); // b2: 10
return 0;
}
数据类型 变量名 = (类型名)变量、常量或表达式;
NOTE
强制类型转换可能会导致精度损失!!!
#include <stdio.h>
int main(){
double d1 = 1.934;
double d2 = 4.2;
int num1 = (int)d1 + (int)d2; // d1 转为 1,d2 转为 4,结果是 5
int num2 = (int)(d1 + d2); // d1+d2 = 6.134,6.134 转为 6
int num3 = (int)(3.5 * 10 + 6 * 1.5); // 35.0 + 9.0 = 44.0 -> int = 44
printf("num1=%d \\n", num1);
printf("num2=%d \\n", num2);
printf("num3=%d \\n", num3);
return 0;
}
表达式
指的是一组运算数、运算符的组合,表达式一定具有值
,一个变量或一个常量可以是表达式,变量、常量和运算符也可以组成表达式,如:操作数
指的是参与运算
的值
或者对象
,如:操作数
的个数
,可以将运算符分为: 功能
,可以将运算符分为: NOTE
掌握一个运算符,需要关注以下几个方面:
运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
---|---|---|---|---|
+ | 正号 | 1 | 操作数本身 | ❎ |
- | 负号 | 1 | 操作数符号取反 | ❎ |
+ | 加号 | 2 | 两个操作数之和 | ❎ |
- | 减号 | 2 | 两个操作数之差 | ❎ |
* | 乘号 | 2 | 两个操作数之积 | ❎ |
/ | 除号 | 2 | 两个操作数之商 | ❎ |
% | 取模(取余) | 2 | 两个操作数相除的余数 | ❎ |
++ | 自增 | 1 | 操作数自增前或自增后的值 | ✅ |
-- | 自减 | 1 | 操作数自减前或自减后的值 | ✅ |
NOTE
自增和自减:
变量前++
:变量先自增 1 ,然后再运算;变量后++
:变量先运算,然后再自增 1 。变量前--
:变量先自减 1 ,然后再运算;变量后--
:变量先运算,然后再自减 1 。#include <stdio.h>
int main() {
int x = 12;
int x1 = -x, x2 = +x;
int y = -67;
int y1 = -y, y2 = +y;
printf("x1=%d, x2=%d \\n", x1, x2); // x1=-12, x2=12
printf("y1=%d, y2=%d \\n", y1, y2); // y1=67, y2=-67
return 0;
}
#include <stdio.h>
int main() {
int a = 5;
int b = 2;
printf("%d + %d = %d\\n", a, b, a + b); // 5 + 2 = 7
printf("%d - %d = %d\\n", a, b, a - b); // 5 - 2 = 3
printf("%d × %d = %d\\n", a, b, a * b); // 5 × 2 = 10
printf("%d / %d = %d\\n", a, b, a / b); // 5 / 2 = 2
printf("%d %% %d = %d\\n", a, b, a % b); // 5 % 2 = 1
return 0;
}
#include <stdio.h>
int main() {
int res1 = 10 % 3;
printf("10 %% 3 = %d\\n", res1); // 10 % 3 = 1
int res2 = -10 % 3;
printf("-10 %% 3 = %d\\n", res2); // -10 % 3 = -1
int res3 = 10 % -3;
printf("10 %% -3 = %d\\n", res3); // 10 % -3 = 1
int res4 = -10 % -3;
printf("-10 %% -3 = %d\\n", res4); // -10 % -3 = -1
return 0;
}
#include <stdio.h>
int main() {
int i1 = 10, i2 = 20;
int i = i1++;
printf("i = %d\\n", i); // i = 10
printf("i1 = %d\\n", i1); // i1 = 11
i = ++i1;
printf("i = %d\\n", i); // i = 12
printf("i1 = %d\\n", i1); // i1 = 12
i = i2--;
printf("i = %d\\n", i); // i = 20
printf("i2 = %d\\n", i2); // i2 = 19
i = --i2;
printf("i = %d\\n", i); // i = 18
printf("i2 = %d\\n", i2); // i2 = 18
return 0;
#include <stdio.h>
/*
随意给出一个整数,打印显示它的个位数,十位数,百位数的值。
格式如下:
数字xxx的情况如下:
个位数:
十位数:
百位数:
例如:
数字153的情况如下:
个位数:3
十位数:5
百位数:1
*/
int main() {
int num = 153;
int bai = num / 100;
int shi = num % 100 / 10;
int ge = num % 10;
printf("百位为:%d \\n", bai);
printf("十位为:%d \\n", shi);
printf("个位为:%d \\n", ge);
return 0;
}
运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
---|---|---|---|---|
== | 相等 | 2 | 0 或 1 | ❎ |
!= | 不相等 | 2 | 0 或 1 | ❎ |
< | 小于 | 2 | 0 或 1 | ❎ |
> | 大于 | 2 | 0 或 1 | ❎ |
<= | 小于等于 | 2 | 0 或 1 | ❎ |
>= | 大于等于 | 2 | 0 或 1 | ❎ |
NOTE
==
写成 =
,==
是比较运算符,而 =
是赋值运算符。>=
或 <=
含义是只需要满足 大于或等于
、小于或等于
其中一个条件,结果就返回真。#include <stdio.h>
int main() {
int a = 8;
int b = 7;
printf("a > b 的结果是:%d \\n", a > b); // a > b 的结果是:1
printf("a >= b 的结果是:%d \\n", a >= b); // a >= b 的结果是:1
printf("a < b 的结果是:%d \\n", a < b); // a < b 的结果是:0
printf("a <= b 的结果是:%d \\n", a <= b); // a <= b 的结果是:0
printf("a == b 的结果是:%d \\n", a == b); // a == b 的结果是:0
printf("a != b 的结果是:%d \\n", a != b); // a != b 的结果是:1
return 0;
}
运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
---|---|---|---|---|
&& | 逻辑与 | 2 | 0 或 1 | ❎ |
|| | 逻辑或 | 2 | 0 或 1 | ❎ |
! | 逻辑非 | 2 | 0 或 1 | ❎ |
a | b | a && b | a || b | !a |
---|---|---|---|---|
1(真) | 1(真) | 1(真) | 1(真) | 0(假) |
1(真) | 0(假) | 0(假) | 1(真) | 0(假) |
0(假) | 1(真) | 0(假) | 1(真) | 1(真) |
0(假) | 0(假) | 0(假) | 0(假) | 1(真) |
NOTE
5 || 0
返回 1
,5 && 0
返回 0
。a && b
操作来说,当 a 为假(或 0 )时,因为 a && b
结果必定为 0,所以不再执行表达式 b。a || b
操作来说,当 a 为真(或非 0 )时,因为 a || b
结果必定为 1,所以不再执行表达式 b。#include <stdio.h>
int main() {
int a = 0;
int b = 0;
printf("请输入整数a的值:");
scanf("%d", &a);
printf("请输入整数b的值:");
scanf("%d", &b);
if (a > b) {
printf("%d > %d", a, b);
} else if (a < b) {
printf("%d < %d", a, b);
} else {
printf("%d = %d", a, b);
}
return 0;
}
#include <stdio.h>
// 短路现象
int main() {
int i = 0;
int j = 10;
if (i && j++ > 0) {
printf("床前明月光\\n"); // 这行代码不会执行
} else {
printf("我叫郭德纲\\n");
}
printf("%d \\n", j); //10
return 0;
}
#include <stdio.h>
// 短路现象
int main() {
int i = 1;
int j = 10;
if (i || j++ > 0) {
printf("床前明月光 \\n");
} else {
printf("我叫郭德纲 \\n"); // 这行代码不会被执行
}
printf("%d\\n", j); //10
return 0;
}
运算符 | 描述 | 操作数个数 | 组成的表达式的值 | 副作用 |
---|---|---|---|---|
== | 赋值 | 2 | 左边操作数的值 | ✅ |
+= | 相加赋值 | 2 | 左边操作数的值 | ✅ |
-= | 相减赋值 | 2 | 左边操作数的值 | ✅ |
*= | 相乘赋值 | 2 | 左边操作数的值 | ✅ |
/= | 相除赋值 | 2 | 左边操作数的值 | ✅ |
%= | 取余赋值 | 2 | 左边操作数的值 | ✅ |
<<= | 左移赋值 | 2 | 左边操作数的值 | ✅ |
>>= | 右移赋值 | 2 | 左边操作数的值 | ✅ |
&= | 按位与赋值 | 2 | 左边操作数的值 | ✅ |
^= | 按位异或赋值 | 2 | 左边操作数的值 | ✅ |
|= | 按位或赋值 | 2 | 左边操作数的值 | ✅ |
NOTE
#include <stdio.h>
int main() {
int a = 3;
a += 3; // a = a + 3
printf("a = %d\\n", a); // a = 6
int b = 3;
b -= 3; // b = b - 3
printf("b = %d\\n", b); // b = 0
int c = 3;
c *= 3; // c = c * 3
printf("c = %d\\n", c); // c = 9
int d = 3;
d /= 3; // d = d / 3
printf("d = %d\\n", d); // d = 1
int e = 3;
e %= 3; // e = e % 3
printf("e = %d\\n", e); // e = 0
return 0;
}
运算符 | 描述 | 操作数个数 | 运算规则 | 副作用 |
---|---|---|---|---|
& | 按位与 | 2 | 两个二进制位都为 1 ,结果为 1 ,否则为 0 。 | ❎ |
| | 按位或 | 2 | 两个二进制位只要有一个为 1(包含两个都为 1 的情况),结果为 1 ,否则为 0 。 | ❎ |
^ | 按位异或 | 2 | 两个二进制位一个为 0 ,一个为 1 ,结果为 1,否则为 0 。 | ❎ |
~ | 按位取反 | 2 | 将每一个二进制位变成相反值,即 0 变成 1 , 1 变 成 0 。 | ❎ |
<< | 二进制左移 | 2 | 将一个数的各二进制位全部左移指定的位数,左 边的二进制位丢弃,右边补 0。 | ❎ |
>> | 二进制右移 | 2 | 将一个数的各二进制位全部右移指定的位数,正数左补 0,负数左补 1,右边丢弃。 | ❎ |
NOTE
操作数在进行位运算的时候,以它的补码形式计算!!!
在 C 语言中,printf
是没有提供输出二进制位的格式占位符的;但是,我们可以手动实现,以方便后期操作。
示例:
#include <stdio.h>
/**
* 获取指定整数的二进制表示
* @param num 整数
* @return 二进制表示的字符串,不包括前导的 '0b' 字符
*/
char* getBinary(int num) {
static char binaryString[33];
int i, j;
for (i = sizeof(num) * 8 - 1, j = 0; i >= 0; i--, j++) {
const int bit = (num >> i) & 1;
binaryString[j] = bit + '0';
}
binaryString[j] = '\\0';
return binaryString;
}
int main() {
int a = 17;
int b = -12;
printf("整数 %d 的二进制表示:%s \\n", a, getBinary(a));
printf("整数 %d 的二进制表示:%s \\n", b, getBinary(b));
return 0;
}
按位与 &
的运算规则是:如果二进制对应的位上都是 1 才是 1 ,否则为 0 ,即:
1 & 1
的结果是 1
。1 & 0
的结果是 0
。0 & 1
的结果是 0
。0 & 0
的结果是 0
。示例:9 & 7 = 1
-9 & 7 = 7
按位与 |
的运算规则是:如果二进制对应的位上只要有 1 就是 1 ,否则为 0 ,即:
1 | 1
的结果是 1
。1 | 0
的结果是 1
。0 | 1
的结果是 1
。0 | 0
的结果是 0
。示例:9 | 7 = 15
-9 | 7 = -9
^
的运算规则是:如果二进制对应的位上一个为 1 一个为 0 就为 1 ,否则为 0 ,即: 1 ^ 1
的结果是 0
。1 ^ 0
的结果是 1
。0 ^ 1
的结果是 1
。0 ^ 0
的结果是 0
。NOTE
按位异或的场景有:
9 ^ 7 = 14
-9 ^ 7 = -16
运算规则:如果二进制对应的位上是 1,则结果为 0;如果是 0 ,则结果为 1 。
~0
的结果是 1
。~1
的结果是 0
。示例:~9 = -10
~-9 = 8
在一定范围内,数据每向左移动一位,相当于原数据 × 2。(正数、负数都适用)
示例:3 << 4 = 48
(3 × 2^4)
-3 << 4 = -48
(-3 × 2 ^4)NOTE
69 >> 4 = 4
(69 ÷ 2^4 )-69 >> 4 = -5
(-69 ÷ 2^4 )条件表达式 ? 表达式1 : 表达式2 ;
NOTE
#include <stdio.h>
int main() {
int m = 110;
int n = 20;
int result = m > n ? m : n;
printf("result = %d\\n", result); // result = 110
return 0;
}
优先级 | 运算符 | 名称或含义 | 结合方向 |
---|---|---|---|
1 | [] | 数组下标 | 左到右 |
() | 圆括号 | ||
. | 成员选择(对象) | ||
-> | 成员选择(指针) | ||
2 | - | 负号运算符 | 右到左 |
(类型) | 强制类型转换 | ||
++ | 自增运算符 | ||
-- | 自减运算符 | ||
* | 取值运算符 | ||
& | 取地址运算符 | ||
! | 逻辑非运算符 | ||
~ | 按位取反运算符 | ||
sizeof | 长度运算符 | ||
3 | / | 除 | 左到右 |
* | 乘 | ||
% | 余数(取模) | ||
4 | + | 加 | 左到右 |
- | 减 | ||
5 | << | 左移 | 左到右 |
>> | 右移 | ||
6 | > | 大于 | 左到右 |
>= | 大于等于 | ||
< | 小于 | ||
<= | 小于等于 | ||
7 | == | 等于 | 左到右 |
!= | 不等于 | ||
8 | & | 按位与 | 左到右 |
9 | ^ | 按位异或 | 左到右 |
10 | | | 按位或 | 左到右 |
11 | && | 逻辑与 | 左到右 |
12 | || | 逻辑或 | 左到右 |
13 | ?: | 条件运算符 | 右到左 |
14 | = | 赋值运算符 | 右到左 |
/= | 除后赋值 | ||
*= | 乘后赋值 | ||
%= | 取模后赋值 | ||
+= | 加后赋值 | ||
-= | 减后赋值 | ||
<<= | 左移后赋值 | ||
>>= | 右移后赋值 | ||
&= | 按位与后赋值 | ||
^= | 按位异或后赋值 | ||
|= | 按位或后赋值 | ||
15 | , | 逗号运算符 | 左到右 |
WARNING
使用小括号来控制
表达式的执行顺序。分成几步
来完成。唯一的编号
(通常是数字)。字符可以是字母、数字、符号、控制代码(如换行符)等。字符集定义了可以表示的字符的范围
,但它并不直接定义如何将这些字符存储在计算机中。NOTE
ASCII(美国信息交换标准代码)是最早期和最简单的字符集之一,它只包括了英文字母、数字和一些特殊字符,共 128 个字符。每个字符都分配给了一个从 0 到 127 的数字。
它定义了如何将字符集中的字符转换为计算机存储和传输的数据(通常是一串二进制数字)
。简而言之,编码是字符到二进制数据之间的映射规则。NOTE
ASCII编码方案定义了如何将 ASCII 字符集中的每个字符表示为 7 位的二进制数字。例如:大写字母"A"
在ASCII 编码中表示为二进制的1000001
,十进制的 65
。
字符集
和字符集编码
之间的关系如下:冯·诺依曼
体系结构中,我们知道,计算机中所有的数据
和指令
都是以二进制
的形式表示的;所以,计算机中对于文本数据的数据也是以二进制来存储的,那么对应的流程如下:NOTE
a-zA-Z0-9
以及一些特殊字符
一共 128
就可以满足实际存储需求;所以,在也是为什么 ASCII 码使用 7 位二进制(2^7 = 128 )来存储的。man ascii
è
、德语中的 ü
等。NOTE
在 Unicode 之前,世界上存在着数百种不同的编码系统,每一种编码系统都是为了支持特定语言或一组语言的字符集。这些编码系统,包括:ASCII、ISO 8859 系列、GBK、Shift-JIS、EUC-KR 等,它们各自有不同的字符范围和编码方式。这种多样性虽然在局部范围内解决了字符表示的问题,但也带来了以下几个方面的挑战:
编码冲突
:由于不同的编码系统可以为相同的字节值分配不同的字符,因此在不同编码之间转换文本时,如果没有正确处理编码信息,就很容易产生乱码。这种编码冲突在尝试处理多种语言的文本时尤为突出。编码的复杂性
:随着全球化的发展,软件和系统需要支持越来越多的语言,这就要求开发者和系统同时处理多种不同的编码系统。这不仅增加了开发和维护的复杂性,而且也增加了出错的风险。资源限制
:在早期计算机技术中,内存和存储资源相对有限。不同的编码标准要求系统存储多套字符集数据,这无疑增加了对有限资源的消耗。针对上述的种种问题,为了推行全球化,Unicode 应运而生,Unicode 的核心规则和设计原则是建立一个全球统一的字符集,使得世界上所有的文字和符号都能被唯一地识别和使用,无论使用者位于何地或使用何种语言。这套规则包括了字符的编码、表示、处理和转换机制,旨在确保不同系统和软件间能够无缝交换和处理文本数据。
通用字符集 (UCS)
:Unicode 为每一个字符分配一个唯一的编号(称为“码点”
)。这些码点被组织在一个统一的字符集中,官方称之为 “通用字符集”(Universal Character Set,UCS)。码点通常表示为 U+
后跟一个十六进制数,例如:U+0041
代表大写的英文字母 “A”
。编码平面和区段
:Unicode 码点被划分为多个 “平面(Planes)”,每个平面包含 65536(16^4)个码点。目前,Unicode定义了 17 个平面(从 0 到16),每个平面被分配了一个编号,从 “基本多文种平面(BMP)” 的 0 开始,到 16 号平面结束。这意味着 Unicode 理论上可以支持超过 110万(17*65536)个码点。Unicode 仅仅只是字符集,给每个字符设置了唯一的数字编号而已,却没有给出这些数字编号实际如何存储,可以通过如下命令查看:
Unicode 字符集
和对应的UTF-8 字符编码
之间的关系,如下所示: