当前位置: 首页 > news >正文

三合一网站开发做ic销售的各种网站

三合一网站开发,做ic销售的各种网站,线上直播营销策划方案,wordpress 安装 乱码C语言技能数#xff08;知识点汇总#xff09; C 语言概述特点不足之处 标准编程机制 数据类型变量数据类型字符类型整数类型符号位二进制的原码、反码和补码 浮点类型布尔类型 常量字面常量const 修饰的常变量#define定义的标识符常量枚举常量 sizeofsizeof(结构体)不要对 v… C语言技能数知识点汇总 C 语言概述特点不足之处 标准编程机制 数据类型变量数据类型字符类型整数类型符号位二进制的原码、反码和补码 浮点类型布尔类型 常量字面常量const 修饰的常变量#define定义的标识符常量枚举常量 sizeofsizeof(结构体)不要对 void 类型使用 sizeof不要在子函数中对指针类型使用 sizeofsizeof 的结果取决于环境 运算符与表达式语句与控制流函数与程序结构函数的声明和定义函数的参数形式参数和实际参数数组作为实参进行传递 函数的返回和嵌套函数的递归变量的作用域局部变量全局变量注意事项 头文件\#include头文件的引用头文件的内容示例 内部函数和外部函数外部函数 (extern)内部函数 (static) 数组数组的创建数组的使用二维数组变长数组 指针指针的声明指针的简单应用NULL 指针指针的算术运算指针数组数组指针指向指针的指针 字符串字符C 的字符串字符串输入与输出scanf 函数gets 函数fgets 函数scanf 和 gets 的区别printf 函数puts 函数fputs 函数 字符串函数strcpystrcatstrlenstrcmpstrchrstrstr C 的字符串 枚举类型结构体声明与定义结构体作为参数指向结构体的指针结构体占用的内存空间大小位域结构体小案例 联合体定义联合体与使用联合体作为参数指向联合体的指针 位运算位和字节进制数十进制转二进制二进制转十进制八进制十六进制 位逻辑运算符与运算或运算|非运算~异或运算^位移运算 或 位运算赋值运算符 预处理器#include#define#ifndef #ifdef #endif#if #elif #else#undef 文件内存管理存储类别自动变量 auto外部变量静态变量static寄存器变量register相关概念作用域(scope)链接(linkage)存储期(storage duration) 存储类别小结 内存动态管理栈上开辟空间动态内存函数 常见动态内存错误 标准函数库数学库三角函数Trigonometric functions指数函数Exponential functions对数函数Logarithmic functions幂函数Power functions平方根square root立方根cube root求斜边hypotenuse取整函数绝对值和最值 通用工具库 C 语言概述 特点 C语言简洁、紧凑、灵活。C语言的核心内容很少只有 32 个关键字9 种控制语句表达方式简练、实用。C语言有一套强有力的运算符达44种具有丰富的数据类型。具有低级语言的特点。具有与汇编语言相近的功能和描述方法如地址运算、二进制数位运算等对硬件端口等资源直接操作可充分使用计算机资源。是一种结构化语言适合于大型程序的模块化设计。预处理命令和预处理程序。可移植性。生成的目标代码质量高。C语言语法限制不严程序设计自由度大。 不足之处 C程序的错误更隐蔽C程序有时会难以理解C程序有时会难以修改 标准 1978年丹尼斯·里奇(Dennis Ritchie)和布莱恩·科尔尼干(Brian Kernighan) 发布了 KR C.1989年美国国家标准局为其指定标准称为 ANSI C也称 C89.1990年国际化标准组织为其指定标准称为 ISO C也称 C90.1999年发布的ISO/IEC 9899:1999标准称为 C99.2011年美国国家标准局采纳了ISO/IEC 9899:2011标准称为 C11.2018年发布的ISO/IEC 9899:2018标准称为 C18. 编程机制 一个典型的C程序编译管道包含预处理、编译、汇编、链接四个环节。 输入 .c 和 .h 等源码文件【预处理】宏展开#include/#define/…【编译器】编译成汇编代码得到 .s 文件【汇编器】输出机器代码得到 .o 或 .obj 文件【链接器】输出可执行文件得到 .exe 文件 数据类型 变量 变量相当于内存中一个数据存储空间的表示通过变量名可以访问到变量(值)。可以把变量看做是一个房间的门牌号通过门牌号我们可以找到房间。 变量使用注意事项 变量表示内存中的一个存储区域不同的数据类型占用的空间大小不一样该区域有自己的名称和类型变量必须先声明后使用该区域的数据可以在同一类型范围内不断变化变量在同一个作用域内不能重名变量三要素 (变量名值数据类型) 。 数据类型 每一种数据都定义了明确的数据类型在内存中分配了不同大小的内存空间使用字节多少表示。 注意 在c中没有字符串类型使用字符数组 char[] 表示字符串在不同系统上部分数据类型字节长度不一样举例int可以占两个字节或四个字节 字符类型 字符类型可以表示单个字符,字符类型是char char是1个字节(可以存字母或者数字)多个字符称为字符串 在C语言中使用 char 数组 表示数组不是基本数据类型而是构造类型。 注意 字符常量是用单引号括起来的单个字符。 例如 char c1 a; char c3 9;C中还允许使用转义字符‘\’来将其后的字符转变为特殊字符型常量。例如char c3 \n; 在C中 char的本质是一个整数。字符型存储到计算机中需要将字符对应的码值整数找出来。字符和码值的对应关系是通过字符编码表决定的。可以直接给char赋一个整数然后输出时会按照对应的ASCII 字符输出。char类型是可以进行运算的相当于一个整数因为它都对应有Unicode码。 整数类型 对于整型和字符型可以使用 signed 和 unsigned 进行修饰。默认是 signed。 类型字节取值下限取值上限signed char1-2^-7即 -1282^7-1 即 127unsigned char102^8-1 即 255signed short2-2^-15 即 -327682^15-1即 32767unsigned short202^16-1 即 65535signed int2或4-2^-15 或 -2^-31215-1或231-1unsigned int2或40216-1或232-1signed long4-2^-312^31-1unsigned long402^32-1 符号位 存放signed类型的存储单元中左边第一位表示符号位。如果该位为0表示该整数是一个正数如果该位为1表示该整数是一个负数。一个32位的整型变量除去左边第一位符号位剩下表示值的只有31个比特位。 注意 各种类型的存储大小与操作系统、 系统位数和编译器有关 目前通用的以64位系统为主。 C语言的整型类型 分为有符号 (signed) 和无符号 (unsigned) 两种 默认是有符号的。 C程序中整型常声明为 int 型 除非不足以表示大数 才使用 long long。 一个 B 等于 8 个 b B 是 Byte 的缩写意思是字节是计算机存储容量的最基本的存储单元。b 是 bit 的缩写意思是比特位是计算机中的存储数据的最小单位。指的是二进制中的一个位数。 一个字节有八位二进制组成即 1 Byte 8 bit。 通常用 Bit 来作数据传输的单位因为物理层、数据链路层的传输对于用户是透明的。通常用 Byte 来作应用层的单位比如表示文件的大小在用户看来就是可见的数据大小。 二进制的原码、反码和补码 原码 将最高位作为符号位0表示正1表示负其它数字位代表数值本身的绝对值的数字表示方式 反码 如果是正数则表示方法和原码一样如果是负数符号位不变其余各位取反则得到这个数字的反码表示形式。 补码 如果是正数则表示方法和原码一样如果是负数则将数字的反码加上1相当于将原码数值位取反然后在最低位加1。 浮点类型 类型存储大小值范围精度float 单精度4 字节1.2E-38 到 3.4E386 位小数double 双精度8 字节2.3E-308 到 1.7E30815 位小数 注意 关于浮点数在机器中存放形式的简单说明,浮点数符号位指数位尾数位 , 浮点数是近视值尾数部分可能丢失造成精度损失。 浮点型使用细节 浮点型常量默认为 double 型 声明 float 型常量时 须后加 f 或 F。浮点型常量有两种表示形式 十进制数形式如 5.12 512.0f .512 (必须有小数点科学计数法形式:如 5.12e2 、 5.12E-2 通常情况下应该使用 double 型因为它比float型更精确。格式化输出时%f 默认输出 6 位小数%.2f 可指定输出 2 位小数。 布尔类型 布尔类型并非是基本类型 C语言标准(C89)没有定义布尔类型所以C语言判断真假时以0为假非0为真 C语言标准(C99)提供了_Bool 型 _Bool 仍是整数类型但与一般整型不同的是_Bool 变量只能赋值为0或1非0的值都会被存储为1。 C99还提供了一个头文件 stdbool.h 定义了 bool 代表 _Bool true 代表1 false代表0 只要导入 stdbool.h 就能方便的操作布尔类型了 , 比如 bool flag false; 常量 C编程中的常量是一些固定的值它在整个程序运行过程中无法被改变。 字面常量 字面常量是直接写出的固定值它包含C语言中可用的数据类型可分为整型常量字符常量等。 如9.9“hello”等就属于这一类常量。 const 修饰的常变量 C语言标准提供了 const 关键字。在定义变量的同时可在变量名之前加上 const 修饰。 const int n 10; int arr[n] { 0 };const 修饰的常变量本质上是变量。但具有常属性不能被修改。在C99标准之前数组的大小只能是常量修饰不支持变长数组。 #define定义的标识符常量 C语言提供了 #define 命令定义标识符常量该标识符常量在程序中是个定值通常用于代表数组容量或涉及数学的常量等。 #define PI 3.14159 #define SIZE 10#define 又称宏定义标识符为所定义的宏名简称宏。对宏定义而言预编译的时候会将程序中所有出现“标识符”的地方全部用这个“常量”替换称为“宏替换”或“宏展开”。被定义的标识符不占内存只是一个临时的符号预编译后这个符号就会被宏替换。宏所表示的常量可以是数字、字符、字符串、表达式。其中最常用的是数字。 枚举常量 语言提供了一种 枚举Enum类型能够列出所有可能会用到的取值并给它们取一个名字 enum Gender { Male, Female, Secret, Unknown };在使用枚举常量的时候需要注意以下几点 不能对枚举常量赋值只能将它们的值赋给其他的变量。不能再定义与枚举常量名字相同的变量。不能用 取得它们的地址。 sizeof sizeof 是关键字操作符不是函数用于获取操作数被分配的内存空间以字节单位表示。 基本使用 sizeof(object);sizeof objectsizeof(type_name); 例如 int n 10; printf(sizeof(n)%d\n, sizeof(n)); printf(sizeof n %d\n, sizeof n ); printf(sizeof(int)%d\n, sizeof(int));sizeof(结构体) 理论上讲结构体的各个成员在内存中是连续存放的和数组非常类似但是结构体占用内存的总大小不一定等于全部成员变量占用内存大小之和。在编译器的具体实现中为了提高内存寻址的效率各个成员之间可能会存在缝隙。用 sizeof 可以得到结构体占用内容在总大小。sizeof(结构体名) 或 sizeof(结构体变量名) 都可以。 不要对 void 类型使用 sizeof void是无值型或空类型不知道存储空间大小的类型编译器也不能确定它的大小。 #include stdio.hvoid func() {}int main() {printf(sizeof(func)%d\n, sizeof(func));// 输出结果sizeof(func)1return 0; }void不能声明变量但是可以声明指针 void *pv; printf(sizeof(pv)%d\n,sizeof(pv)); printf(sizeof(void*)%d\n,sizeof(void *)); // 输出结果 // sizeof(pv)4 // sizeof(void*)4不要在子函数中对指针类型使用 sizeof 如果把一个字符串的地址传给子函数子函数用一个字符指针如char *pstr来存放传入的字符串的地址如果在子函数中用 sizeof(pstr)得到的不是字符串占用内存的字节数而是字符指针变量占用内存的字节数。 所以不能在子函数中对传入的字符串进行初始化除非字符串的长度也作为参数传入到了子函数中。 #include stdio.hvoid func(char * pstr) {// sizeof(pstr)4printf(sizeof(pstr)%d\n, sizeof(pstr)); }int main() {func(hello world!);return 0; }同理也不要在子函数中对结构体指针用 sizeof如果把一个结构体如struct Human h的地址传给子函数子函数用一个结构体指针如struct Human *h来存放传入的结构体的地址如果在子函数中用 sizeof(h)得到的不是结构体占用内存的字节数而是结构体指针变量占用内存的字节数。 正确的用法是用 sizeof(struct Human) 。 #include stdio.hstruct Human {char * name; int age;};void func(struct Human *h) {// 错误的输出sizeof(h)4printf(sizeof(h)%d\n, sizeof(h));// 正确的输出sizeof(*h)8printf(sizeof(*h)%d\n, sizeof(*h));// 正确的输出sizeof(struct Human)8printf(sizeof(struct Human)%d\n, sizeof(struct Human)); }int main() {struct Human human {tom, 23};func(human);return 0; }sizeof 的结果取决于环境 在不同的 GCC 环境中得到的结果是不同的。 通过 systeminfo 指令可以获取当前系统的信息部分如下 主机名: THINKPADX1 OS 名称: Microsoft Windows 11 专业版 OS 制造商: Microsoft Corporation OS 配置: 独立工作站 系统制造商: LENOVO 系统类型: x64-based PC可以看出设备是基于 x64 的芯片也就是收机器字长是 64。通过下面这段源代码观察不同 gcc 下的结果 #includestdio.hint main() {char c A;short s 10; int i 10; long l 10L;float f 3.14F; double d 3.14D;int *p i;printf(char c %c\t, c);printf(short s %d\t, s);printf(int s %d\t, i);printf(long s %d\n, l);printf(float s %f\t, f);printf(double s %f\n, d);printf(*p %d\n, *p);printf( p %p\n, p);printf(p %p\n, p);printf(sizeof(char) %d\n, sizeof(char));printf(sizeof(short) %d\n, sizeof(short));printf(sizeof(int) %d\n, sizeof(int));printf(sizeof(long) %d\n, sizeof(long));printf(sizeof(float) %d\n, sizeof(float));printf(sizeof(double) %d\n, sizeof(double));printf(sizeof(size_t) %d\n, sizeof(size_t));// size_t是一种机器相关的无符号类型它被设计的足够大以便能表示内存中任意对象的大小。return 0; }我已经在本机的 D:\Program\MinGW 和 D:\Program\MinGW64 分别安装了 gcc 的32位版本和 64位版本。 在命令行中临时修改环境变量用以切换 gcc 的寻址路径 SET PATHC:\Windows\System32\;D:\Program\MinGW\binWHERE gcc D:\Program\MinGW\bin\gcc.exe也可以通过 gcc -v 指令来查看当前 gcc 的版本接下来使用 gcc 对上面的程序进行编译和执行如下 gcc app.c -o app.exe app.exe执行结果 char c A short s 10 int s 10 long s 10 float s 3.140000 double s 3.140000 *p 10p 0061FF04 p 0061FF00 sizeof(char) 1 sizeof(short) 2 sizeof(int) 4 sizeof(long) 4 sizeof(float) 4 sizeof(double) 8 sizeof(size_t) 4然后在命令行中通过再次修改环境变量来临时切换下 gcc 的寻址位置如下所示 SET PATHC:\Windows\System32\;D:\Program\MinGW64\binWHERE gcc D:\Program\MinGW64\bin\gcc.exe再次使用 gcc 命令对上面的程序进行编码和执行执行结果如下 char c A short s 10 int s 10 long s 10 float s 3.140000 double s 3.140000 *p 10p 000000000061FE04 p 000000000061FDF8 sizeof(char) 1 sizeof(short) 2 sizeof(int) 4 sizeof(long) 4 sizeof(float) 4 sizeof(double) 8 sizeof(size_t) 8在基于 x64 的操作系统中安装两个不同版本的 gcc 使用 sizeof 对基础类型测量的占用空间得到结论如下 typeGCC x32GCC x64char11short22int44long44float44double88pointer816size_t48 可以明显看到 基础数据类型占用的空间不会因为 gcc 的版本不同而不同的即便是基于 x64 的平台在 gcc 32 位的环境下指针的长度也为 8 运算符与表达式 赋值运算符号 、、-、*、/、%算术运算符号 、-、*、/、%自运算符号 和 -- 前缀先运算后赋值后缀先赋值后运算 关系运算符号 、、、、、!逻辑运算符号 与()、或(||)、非(!)逗号运算符号 一个句子就会像一个函数一样有返回值如果用逗号隔开 这个【返回值】就会变成最后那个表达式的值 #include stdio.hint main() {3, 4, 5;//这是一条语句int n (3, 4, 5); // n 的值 5printf(n%d\n, n);int a3, b4, c5;int x0;int y((xab),(bc));// 等价于 ybc 致使 y 的值是 9printf(x%d,y%d\n, x, y);y(xab),(bc);// 等价于 yxab 致使 y 的值是 7printf(x%d,y%d\n, x, y);return 0; }条件运算符号 int num 2024; printf(num is %s\n, num % 2 0 ? even : odd);语句与控制流 控制语句 // for 循环 for(int i 0; i 5; i) {printf(%d\t, i); } printf(for end\n);// do 循环 int i 0; do {printf(%d\t, i);i; } while(i 5); printf(do end\n);// while 循环 i 0; while(i 5) {printf(%d\t, i);i; } printf(while end\n); 函数返回(return) int main() {return 0; }转向语句 // for 循环 for(int i 0; i 5; i) {printf(%d\t, i);if(i 2) {goto end_flag;} } printf(for end\n);end_flag: printf(finish!\n);函数调用语句 printf(“Hello world.”);表达式语句 i;空语句 直接只有一个分号的一行语句。 复合语句也称为代码块或者语句块 // 代码块 {int i 10;printf(i%d\n, i); } // 复合语句 {int i 20;printf(i%d\n, i); }函数与程序结构 被调用的函数必须在调用行之前被定义 函数的声明和定义 在 C 语言中一般地一个函数由函数头和函数体两部分组成。一般形式如下 返回值类型 函数名 (参数1, 参数2, ...) {// 代码 }上述形式中大括号部分就是函数体除了大括号的那一部分就被称之为函数头。 如果只有函数的声明而没有函数的定义那么程序将会在链接时出错 void fun1() {} // 该行是对函数 fun1 进行的定义 void fun2(); // 该行只是函数声明并没有对该函数进行定义int main() {return 0; }// 如下代码是对 fun2 函数进行具体的定义如果不定义 fun2, 会在链接环节出现错误。 void fun2() {// statements here... }函数的参数 在定义函数的时候可以在小括号中定义一系列变量这些变量被称之为函数的参数。 这一系列变量就构成了函数的参数列表它定义了该函数可以接纳那些参数的输入。 参数列表和函数名一起构成了函数签名。 形式参数和实际参数 形式参数 形参出现在被调函数当中在整个函数体内都可以使用。 形参在定义时编译系统并不分配存储空间只有在调用该函数时才分配内存单元。 调用结束内存单元被释放故形参只有在函数调用时有效调用结束时不能再使用。 实际参数 实参出现在主调函数当中当函数调用时主调函数把实参的值传送给被调函数的形参从而实现函数间的数据传递。 实参与形参必须个数相同对应的形参和实参的类型必须一致 实参的传递方式 在调用函数的时候数据从实参传递给形参可以使得函数在内部读取或使用函数外部的变量的数据。传递的方式主要有两种传值和传址 传值值传递 值传递的特点是将实参的数据拷贝给形参形参的修改并不会影响到实参本身因为修改的是不同的地址。 传址址传递 址传递的特点是将实参的地址传递给形参形参的修改会同步影响到实参本身因为修改的是相同的地址。 数组作为实参进行传递 传递数组元素 数组元素也称之为下标变量作为函数的参数进行的数据传递是值传递方式。 传递数组名称 数组名即数组首地址、数组元素的地址arr[0]作为函数参数进行的数据传递是地址传递方式。 #include stdio.hvoid func(int *p) {printf(p%p\n, p); } int main() {int ns[] {3,4,5};func(ns);func(ns); // warning: expected int * but argument is of type int (*)[3]func(ns[0]);return 0; }上述三种传递的方式形参 p 接收到的是同一个地址如果 p 修改了地址中的值ns 中的值也会跟着变化。 函数的返回和嵌套 返回值类型如果函数需要返回一个值则需要为其指定具体的类型 函数的嵌套定义函数时不能定义另一个函数但是可以进行嵌套调用函数。 如果需要从调用函数带回一个函数值供主函数使用被调函数中需包含return语句函数的返回值通过函数中的 return 语句传递到调用行。在定义函数时要指定函数值的类型函数类型决定返回值的类型 int func(int a, int b) {return a b; }函数的递归 函数的递归调用是指一个函数在他的函数体内直接或间接地调用它自身。 分为直接递归函数直接调用自身和间接递归函数通过其他函数调用自身。 可分为“回溯”和“递推”两个阶段。 #includestdio.h// 阶乘一个正整数的阶乘是所有小于及等于该数的正整数的积并且0的阶乘为1。自然数n的阶乘写作n!。 int factorial(int a) {if(a 2) {return 1;}return a * factorial(a - 1); }int main() { printf(5!%d\n, factorial(5));return 0; }变量的作用域 局部变量 首先它是一个变量其次这个变量只是在程序的局部范围内有效局部变量定义可以定义在如下位置 函数的开头函数内的复合语句内定义形式参数函数中间非开头 程序执行到某个函数时这个函数内部的局部变量将会被分配内存空间局部变量在函数执行结束后变量所占内存将会被释放 全局变量 首先它是变量其次它可以在全局范围内是有意义的变量 所谓全局也并不是真正的全局而是在定义处以下的范围内才是有效的全局变量定义的位置: 文件开头函数前函数后文件结尾 注意事项 为了区别全局变量和局部变量往往大家在写程序的时候都喜欢将全局变量的首字母大写而局部变量的首字母小写 全局变量的优点和缺点 **优点**C语言的函数每次最多只能返回一个值但是如果定义了全局变量那么在这个变量的有效范围内很多函数都能改变这个变量的值所以增加了函数之间的联系通过函数的调用可以得到一个或一个以上的值 缺点大量使用全局变量的情况下 占内存全局变量所占的内存空间不会像局部变量一样会被释放降低程序清晰性无法随时确定定义的全局变量的值的大小降低通用性程序设计时要求函数的“内聚性”强函数与函数之间“耦合性”弱定义全局变是一定要注意在有效范围内变量不能重名并且当全局变量被跨文件调用的函数调用时不能出现全局变量与所跨文件中存在重名变量否则有可能会出错所以为了提高程序的可靠性可移植性和可读性等全局变量尽量少用 头文件 #include #include 是C语言的预处理指令之一在编译之前做的处理预处理指令一般以 #开头#include 指令后面会跟着一个文件名预处理器发现 #include 指令后就会根据文件名去查找文件并把这个文件的内容包含到当前文件中。被包含文件中的文本将替换源文件中的 #include 指令就像是把被包含文件中的全部内容拷贝到这个 #include 指令所在的位置一样。所以第一行的 #include stdio.h 指令的作用是将 stdio.h 文件里面的所有内容拷贝到第一行中。如果被包含的文件拓展名为 .h我们称之为 头文件 (Header File)头文件可以用来声明函数要想使用这些函数就必须先用 #include 指令包含函数所在的头文件#include 指令不仅仅限于 .h 文件可以包含任何编译器能识别的 C/C 代码文件如 .c、.hpp、.cpp 等甚至 .txt、.abc 等文本文件都可以#include 使用的是相对路径也可以使用绝对路径。比如#include /Users/apple/Desktop/my.txt #include 和 #include 的区别 二者的区别在于当被include的文件路径不是绝对路径的时候有不同的搜索顺序。 对于使用双引号 来 include 文件搜索的时候按以下顺序 先在这条 include 指令所在的文件的所在文件夹内搜索如果上一步找不到则在该文件的父级目录内搜索如果上一步找不到则在编译器设置的 include 路径内搜索如果上一步找不到则在系统的 INCLUDE 环境变量内搜索 对于使用尖括号 来 include 文件搜索的时候按以下顺序 在编译器设置的 include 路径内搜索如果上一步找不到则在系统的 INCLUDE 环境变量内搜索 头文件的引用 一般地系统提供的头文件用 引用 自己写的用 引用。 include 是可以包含 .c 源文件的在某些工程里可以看到但是这样的做法不常见也不推荐include 关键字包含 .c 源文件和 .h 头文件理解都是一样的在原地将引用的文件展开 头文件的内容 头文件里一般包括宏定义 全局变量 函数原型声明。 头文件名的格式为 _头文件名_ 注意要大写 #ifndef 头文件名 #define 头文件名头文件内容#endif示例 头文件app.h #ifndef _APP_H #define _APP_Hvoid func(int n);#endif源文件app.c #include stdio.h #include app.hint main() {func(10);return 0; }void func(int n) {printf(n%d\n, n); }可以使用 gcc app.c 即可对其进行编译#include 指令会自动找到同目录下的 .h 文件 内部函数和外部函数 函数的调用一般是对同一个源文件中的其他函数进行调用的也可以对另外一个源文件中的函数进行调用 C语言中根据函数能否被其他源文件调用分为内部函数和外部函数 外部函数可以被其他源文件调用的函数 内部函数只在定义的文件中有效 外部函数 (extern) 定义外部函数的方式在函数的返回值类型前面添加 extern 关键字 extern int add(int x,int y);举例来说外部函数 第一个源文件 other.c #include stdio.hint add(int a, int b) {int c a b;printf(call add(%d,%d)%d\n, a, b, c);return a b; }第二个源文件 app.c // 该行的 extern 可省略 // 该行也可以直接删除但不显式声明 add 函数的话编译器会报警告 extern int add(int x,int y); int main() {add(3,4);return 0; }在调用方的源文件中可以不用 #include 指令来引入定义方的源文件使用 gcc 对这两个文件进行编译即可 gcc other.c app.c编译器通过 extern 关键字会明确地知道add 函数是定义在其他文件中的外部函数。 在本例中app.c 作为调用方省略 extern 关键字也可以正常运行。但是必须在调用方的源文件中声明需要调用的函数的声明否则会在编译时报警告但依然能输出调用结果如下所示 app.c: In function main: app.c:6:2: warning: implicit declaration of function add [-Wimplicit-function-declaration]add(3,4);^~~内部函数 (static) 只在定义的文件中有效这类函数称为内部函数。 在定义内部函数时需要在函数的返回值类型前面添加 static 关键字也称静态函数。 举个例子来说明内部函数 第一个源文件 other.c #include stdio.hvoid func() {printf(func in other.c\n); }第二个源文件 app.c #include stdio.hstatic void func() {printf(func in app.c\n); }int main() {func();return 0; }在调用方的源文件中可以不用 #include 指令来引入定义方的源文件使用 gcc 对这两个文件进行编译即可 gcc other.c app.c编译器通过 static 关键字会明确地知道func 函数是仅供内部使用的。故而在 main 方法中调用 func 函数的时候输出的结果自然是 func in app.c 。 如果将第二个文件中的 static 去掉会在编译过程中抛出 multiple definition 的错误信息。致使无法得到最后的目标程序。 如果在 app.c 中直接删除 func 函数的定义输出的结果就会是 func in other.c 。但同时也会出现警告因为 main 中的 func 是作为外部函数来进行调用的而 app.c 中又不存在该函数的声明。 思考如果在 other.c 中定义一个函数默认是可以在 app.c 中作为外部函数进行调用的那使用 static 修饰后在 app.c 中又显示注明需要调用外部函数会出现什么结果呢 第一个源文件 other.c #include stdio.hstatic void func() {printf(func in other.c\n); }第二个源文件 app.c extern void func();int main() {func();return 0; }执行编译后出现 undefined reference to func 的异常编译不通过删除 other.c 中的 static 关键字删除后就可以正常编译通过了。编译器通过 extern 关键字知道调用方源文件中需要使用外部函数 func 但是能够在 other.c 中被找到的 func 函数使用了 static 关键字修饰也就不能允许外部源文件调用。故而将该函数视作是未定义进而抛出编译时异常。 数组 数组是一组相同类型元素的集合。 若将有限个类型相同的变量的集合命名那么这个名称为数组名。 组成数组的各个变量称为数组的分量也称为数组的元素有时也称为下标变量。 用于区分数组的各个元素的数字编号称为下标下标从零开始。 数组的创建 一维数组创建示例 int ns[5]; // 数组创建[]中要给一个常量才可以不要使用变量。 const int N 10; int ms[N];数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。 char cs1[] abc; char cs2[3] {a,b,c};数组的使用 使用下标引用操作符 [] 能对数组中的元素进行索引下标访问。 使用 sizeof 可以量出数组的长度和某个元素的长度以此来计算数组的长度。 int ns[] {2, 5, 4}; int len sizeof(ns) / sizeof(ns[0]); printf(sizeof(ns)%d\n, sizeof(ns)); printf(sizeof(ns[0])%d\n, sizeof(ns[0])); printf(len%d\n, len);数组的大小可以通过计算得到。数组在内存中是连续存放的。 二维数组 二维数组可以视作是一个一维数组嵌套一个一维数组。表现形式上可以理解为是一个矩阵。 int ns1[3][4]; // ns1 可视作 3x4 的矩阵如下所示 // □ □ □ □ // □ □ □ □ // □ □ □ □ int ns2[2][3] {3,5,7}; // ns2 可视作 2x3 的矩阵如下所示 // 3 5 7 // □ □ □ int ns3[3][3] {{2,4},{6}}; // ns3 可视作 3x3 的矩阵如下所示 // 2 4 □ // 6 □ □ // □ □ □// 双层循环以遍历二维数组 for(int i0;i3;i) {for(int j0;j3;j) {printf((%d,%d)%d\t, i, j, ns3[i][j]);}printf(\n); }数组越界 数组的下标是有范围限制的。数组的下规定是从0开始的如果输入有n个元素最后一个元素的下标就是n-1。所以数组的下标如果小于0或者大于n-1就是数组越界访问了超出了数组合法空间的访问。C语言本身是不做数组下标的越界检查编译器也不一定报错但是编译器不报错并不意味着程序就是正确的所以程序员写代码时最好自己做越界的检查。二维数组的行和列也可能存在越界。 变长数组 数组变量本身表达的就是地址。在 C 中数组变量就是一个特殊常量指针也称之为数组指针。 可以利用 malloc 进行动态创建指定长度的数组如下所示 #includestdio.h #includestdlib.hint main(void) {int size 12, i0;int* const p (int*)malloc(size*(sizeof(int)));// 为所有下标位置赋值for (i0;isize; i) {p[i] i;}// 输出所有下标位置的值for (i 0; i size; i) {printf(%d,, p[i]);}free(p);return 0; }指针 在计算机科学中指针Pointer是编程语言中的一个对象。 它的值直接指向points to存在电脑存储器中另一个地方的值。 由于通过地址能找到所需的变量单元可以说地址指向该变量单元。 因此将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元。 指针的声明 指针是一个变量其值是另一个变量的地址即内存位置的直接地址。就像其他变量或常量一样必须在使用指针存储其他变量地址之前对其进行声明。指针变量声明的一般形式为type* var-name; 如下例所示 #includestdio.hint main() {// 声明一个 int 类型的指针int* ip;// 声明一个 long 类型的指针long *lp;// 声明一个 double 类型的指针double* dp;// 声明一个 float 类型的指针float* fp;printf(size of int pointer is %d.\n, sizeof(ip));printf(size of long pointer is %d.\n, sizeof(lp));printf(size of double pointer is %d.\n, sizeof(dp));printf(size of float pointer is %d.\n, sizeof(fp));return 0; }执行结果 size of int pointer is 4. size of long pointer is 4. size of double pointer is 4. size of float pointer is 4.在声明的时候* 无论是偏向数据类型的关键字还是偏向变量名称都是被认为是合法的。也就是说int *ip; 和 int* ip; 都是正确的声明方式。但是当多个变量在同一个声明语句时候需要使用 *ip 的形式。例如 int *a, *b; // 声明两个 int 类型的指针变量分别是 a 和 b int a, *b, c; // 声明一个 int 类型的指针变量 b变量 a 和 c 都是普通的 int 变量。因为指针变量存储的值是一个地址值所以无论什么类型的指针都不会影响其本身需要占用内存的空间。由于指针变量接受的是地址值所以在给指针变量赋值的时候需使用到取址符 如下例所示 #includestdio.hint main() {int number 10;int* ip number;printf(number is %d\n, number);printf(number is %d\n, number);printf(ip is %d\n, ip);printf(ip is %d\n, ip);printf(*ip is %d\n, *ip);return 0; }执行结果 number is 10 number is 2293564 ip is 2293564 ip is 2293560 *ip is 10既然指针也是变量那根据上面得到的结果对照到表格中来看 变量名称变量的类型变量的值变量的地址为变量赋值int number 10;numberint102293564number 10int* ip number;ipint*22935642293560ip number 指针是一种特殊的变量其存储的数据不是一个可以直接被人识别的数字或者文本而是某个其他变量的内存地址值。 指针的简单应用 借助指针可以对该内存地址的数据进行读写。如如下例 #includestdio.hint main() {int a 10;int* ip a;printf(a is %d,\t a is %d\n, a, a);printf(ip is %d \t ip is %d \t *ip is %d\n, ip, ip, *ip);// 修改指针所指向的内存地址2293564中的值*ip 100;printf(a is %d,\t a is %d\n, a, a);printf(ip is %d \t ip is %d \t *ip is %d\n, ip, ip, *ip);return 0; }执行结果 a is 10, a is 2293564 ip is 2293564 ip is 2293560 *ip is 10 a is 100, a is 2293564 ip is 2293564 ip is 2293560 *ip is 100这样不需要变量 a 就能实现修改变量 a 所存储的值。 在执行过程中也可以修改指针的存储的地址值为其他的变量。如下所示 #includestdio.hint main() {int a 10, b 20;int* ip a;printf(a is %d,\t a is %d\n, a, a);printf(b is %d,\t b is %d\n, b, b);printf(ip is %d \t ip is %d \t *ip is %d\n, ip, ip, *ip);*ip * 2;printf(a is %d,\t a is %d\n, a, a);printf(b is %d,\t b is %d\n, b, b);printf(ip is %d \t ip is %d \t *ip is %d\n, ip, ip, *ip);ip b;*ip * 3;printf(a is %d,\t a is %d\n, a, a);printf(b is %d,\t b is %d\n, b, b);printf(ip is %d \t ip is %d \t *ip is %d\n, ip, ip, *ip);return 0; }执行结果 a is 10, a is 2293564 b is 20, b is 2293560 ip is 2293564 ip is 2293556 *ip is 10 a is 20, a is 2293564 b is 20, b is 2293560 ip is 2293564 ip is 2293556 *ip is 20 a is 20, a is 2293564 b is 60, b is 2293560 ip is 2293560 ip is 2293556 *ip is 60这里的指针 ip 先指向的是变量 a 的地址在对该地址的数据进行累乘操作后指向了变量 b 的地址又对变量 b 的地址中的值进行了累乘操作。最终 a 的值被乘以 2b 的值被乘以3。 NULL 指针 如果在声明一个指针的时候没有为其赋予确切的地址值那指针可能会指向一个未知的地址。如下例所示 #includestdio.hint main() {int *a, *b;printf(a%d, *a%d, b%d, *b%d\n, a, *a, b, *b);return 0; }执行结果 a2293540, *a4200720, b2147344384, *b0在没有为指针变量 a 和 b 赋予地址值的时候既然还能有值并且能读取到该地址里的值。因为一旦指针存储了地址值后可以对该地址进行修改操作可能会出现一些不该出现的现象比如某个地方的变量的值因为这个指针的原因被修改了之类的。 如果没有确切的地址可以赋值为指针变量赋一个 NULL 值作为初始值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。 因为在 C 中把任何非零和非空的值假定为 true把零或 null 假定为 false所以当一个指针是空指针的时候是可以被判断的。这也有利于后面更加顺利地操作指针。代码如下 #includestdio.hint main() {int a 10;int* ip NULL;printf(a is %d,\t a is %d\n, a, a);if(ip) {printf(ip is not null pointer.\n);} else {printf(ip is null pointer.\n);ip a;}*ip * 2;printf(ip is %d \t ip is %d \t *ip is %d\n, ip, ip, *ip);return 0; }执行结果 a is 10, a is 2293564 ip is null pointer. ip is 2293564 ip is 2293560 *ip is 20当然在该程序中 ip 自然是为空指针但当 ip 作为一个函数参数的时候对指针判空的处理就很重要了因为不知道外部传递的指针到底是否是空的。 指针的算术运算 指针是一种存储其他变量地址值的特殊变量同时也能进行简单的算数运算和逻辑运算。运用自运算遍历数组一种常见的指针应用。 #includestdio.hint main() {int i, size 7;int nums[7] {101, 202, 303, 404, 505, 606, 707};int *p NULL;p nums;for(i 0; i size; i) {printf(nums[%d] %d, p%d, *p%d\n, i, nums[i], p, *p);p;}return 0; }执行结果 nums[0] 101, p2293528, *p101 nums[1] 202, p2293532, *p202 nums[2] 303, p2293536, *p303 nums[3] 404, p2293540, *p404 nums[4] 505, p2293544, *p505 nums[5] 606, p2293548, *p606 nums[6] 707, p2293552, *p707因为数组变量在内存中是以连续的内存空间来存储数据的故而p nums; 等价于 p nums[0]; 也就是说将数组 nums 的地址值赋予给指针等价于将数组中第一个元素的地址值赋值给指针。 从输出的结果上来看也会发现连续的每个元素的地址相差 4 位这个值正是在开篇使用 sizeof 量出来的指针的大小是一致的。 那既然 p nums; 等价于 p nums[0]; 那将数组中最后一个元素的地址值赋予给指针做自减运算输出会怎么样。代码如下 #includestdio.hint main() {int i, size 7;int nums[7] {101, 202, 303, 404, 505, 606, 707};int *p NULL;p nums[size - 1];for(i size - 1; i -1; i--, p--) {printf(nums[%d] %d, p%d, *p%d\n, i, nums[i], p, *p);}return 0; }执行结果 nums[6] 707, p2293552, *p707 nums[5] 606, p2293548, *p606 nums[4] 505, p2293544, *p505 nums[3] 404, p2293540, *p404 nums[2] 303, p2293536, *p303 nums[1] 202, p2293532, *p202 nums[0] 101, p2293528, *p101因为数组中每个元素的地址是连续的当指针指向第一个元素的时候只要指针指向的地址没有超过最后一位就能进行循环取值。这里就需要对指针中存储的地址值进行比较示例代码如下 #includestdio.hint main() {int size 7;int nums[7] {101, 202, 303, 404, 505, 606, 707};int *p nums; // 等价于 nums[0]while(p nums[size - 1]) {printf(%d , *p);p;}return 0; }执行结果 101 202 303 404 505 606 707指针数组 指针数组往往是用于存储一系列相同类型的指针的集合。其声明的一般形式为type* var-name[size];例如 int* ps[3];在理解指针的时候可以类比变量同样的理解指针数组可以类比数组。 数组名称数组中每个元素的类型获取下标为0的元素的值获取下标为0的元素的地址值为下标为0的元素赋值int nums[3];numsintnums[0]nums[0]nums[0] 10;int* ps[3];psint*ps[0]ps[0]ps[0] nums[0] 这样一来很容易理解指针数组了它就是一系列指针的集合。他们不一定是连续的就像数组中的数据不一定是连续的数字的道理一样。这里使用一个案例来对指针数组和普通数组进行对比 #includestdio.hint main() {int i, a 101, b 202, c 303, size 3;int nums[3];nums[0] a;nums[1] b;nums[2] c;int* ps[3];ps[0] a;ps[1] b;ps[2] c;// 输出各个变量的值for(i 0; i size; i) {printf(nums[%d]%d, nums[%d]%d\n, i, nums[i], i, nums[i]);} printf(\na%d, a%d, b%d, b%d, c%d, c%d\n\n, a, a, b, b, c, c);for(i 0; i size; i) {printf(ps[%d]%d, *ps[%d]%d, ps[%d]%d\n, i, ps[i], i, *ps[i], i, ps[i]);}// 修改指针指向的地址中的值for(i 0; i size; i) {*ps[i] * 10;}// 再次输出各个变量的值printf(\n------------------\n);for(i 0; i size; i) {printf(nums[%d]%d, nums[%d]%d\n, i, nums[i], i, nums[i]);} printf(\na%d, a%d, b%d, b%d, c%d, c%d\n\n, a, a, b, b, c, c);for(i 0; i size; i) {printf(ps[%d]%d, *ps[%d]%d, ps[%d]%d\n, i, ps[i], i, *ps[i], i, ps[i]);}return 0; }执行结果 nums[0]101, nums[0]2293536 nums[1]202, nums[1]2293540 nums[2]303, nums[2]2293544a101, a2293556, b202, b2293552, c303, c2293548ps[0]2293556, *ps[0]101, ps[0]2293524 ps[1]2293552, *ps[1]202, ps[1]2293528 ps[2]2293548, *ps[2]303, ps[2]2293532------------------ nums[0]101, nums[0]2293536 nums[1]202, nums[1]2293540 nums[2]303, nums[2]2293544a1010, a2293556, b2020, b2293552, c3030, c2293548ps[0]2293556, *ps[0]1010, ps[0]2293524 ps[1]2293552, *ps[1]2020, ps[1]2293528 ps[2]2293548, *ps[2]3030, ps[2]2293532从执行结果来看指针数组中存储的三个指针修改这三个指针对应地址的值会影响变量 a, b, c但是不影响数组 nums。因为在给数组赋值的时候是将变量 a, b, c 的值赋予了数组 nums也就是 传值。给指针数组赋值的时候是将变量 a, b, c 的地址值赋予了 ps也就是 传址。这样就更加好理解指针数组了数组是一系列相同数据类型的数据的集合而指针数组是一系列相同数据类型的指针的集合。 数组指针 在 C 中数组到底是什么 现象一 在上文中的 指针的算术运算 段落中的内容可以知道p nums; 等价于 p nums[0];。 分析 赋值运算符 两边的数据类型在一致的时候才能将右值赋予左边的变量如果不是因为数据类型发生了隐式转换那就是符号两边的数据类型本就是一致的。这是不是能说明数组变量 nums 就是一个指针如果是这样那这个指针存储的值难道是数组中第一个元素的地址如果是这样那就能解释为什么在给指针 p 赋值的时候nums 不需要 作为前缀了。 现象二 回顾在之前的变量学习中可以知道变量的赋值可以是这样的 int a 10; int b 20; b a;最终变量 b 的值变成了 20那同样的代码数组能这样使用吗比如 int a[] {1, 2}; int b[] {3, 4}; b a;程序在编译的时候会抛出一个错误信息 error: assignment to expression with array typeb a;^分析 为什么这里会提示数组类型的变量在声明之后不允许使用赋值表达式这个现象和常量很相似常量在定义完成之后也是不允许为其赋值。这是不是能说明数组是一个常量 解释 在 C 中数组变量就是一个特殊常量指针也称之为数组指针。它有如下特点 数组变量本身表达的就是地址。所以 nums nums[0]运算符 [] 可以对数组做运算也可以对指针做运算。所以 p[0] 等价于 nums[0]运算符 * 可以对指针做运算也可以对数组做运算。所以 *nums 是被允许的数组变量是 const 的指针所以不允许被赋值。也就是说 int nums[] 等价于 int * const nums 实例 #includestdio.h#define SIZE 6 int main() {int nums[SIZE] {101, 202, 303, 404, 505, 606};int *p nums;// nums 和 p 都能使用运算符 *printf(*p%d, *nums%d\n, *p, *nums);printf( p%p, nums%p\n, p, nums);printf(p%p, nums%p\n, p, nums);// nums 和 p 都能使用逻辑运算符printf(p nums[1] is %s\n, p nums[1] ? true : false);printf(nums nums[1] is %s\n, nums nums[1] ? true : false);// nums 和 p 都能使用下标来操作元素for(int i 0; i SIZE; i) {printf(nums[%d]%d, nums[%d]%p, p[%d]%d, p[%d]%p\n, i, nums[i], i, nums[i], i, p[i], i, p[i]);}// nums 是常量指针故而不能做自运算while(p nums[SIZE - 1]) {p;// error: lvalue required as increment operand// nums;printf(p%p, *p%d, nums%p, *nums%d\n, p, *p, nums, *nums);}return 0; }执行结果 *p101, *nums101p0022FF24, nums0022FF24 p0022FF20, nums0022FF24 p nums[1] is true nums nums[1] is true nums[0]101, nums[0]0022FF24, p[0]101, p[0]0022FF24 nums[1]202, nums[1]0022FF28, p[1]202, p[1]0022FF28 nums[2]303, nums[2]0022FF2C, p[2]303, p[2]0022FF2C nums[3]404, nums[3]0022FF30, p[3]404, p[3]0022FF30 nums[4]505, nums[4]0022FF34, p[4]505, p[4]0022FF34 nums[5]606, nums[5]0022FF38, p[5]606, p[5]0022FF38 p0022FF28, *p202, nums0022FF24, *nums101 p0022FF2C, *p303, nums0022FF24, *nums101 p0022FF30, *p404, nums0022FF24, *nums101 p0022FF34, *p505, nums0022FF24, *nums101 p0022FF38, *p606, nums0022FF24, *nums101指向指针的指针 我们知道指针存储的是其他变量的的地址值而指针本身也是一个变量那一个指针指向的变量正好也是一个指针变量呢这种情况被称之为指向指针的指针。 指向指针的指针在声明的时候必须比被指向的指针变量多一个星号如下所示 #includestdio.hint main() {int num 10;int *p num;int **ip p;int ***ipp ip;printf(num%d, num%d\n, num, num);printf(p%d, *p%d, p%d\n, p, *p, p);printf(ip%d, *ip%d, ip%d\n, ip, *ip, ip);printf(ipp%d, *ipp%d, ipp%d\n, ipp, *ipp, ipp);return 0; }执行结果 num10, num2293564 p2293564, *p10, p2293560 ip2293560, *ip2293564, ip2293556 ipp2293556, *ipp2293560, ipp2293552如果上例中的指针 ipp 指向指针 p 会在编译的时候抛出一个警告 warning: initialization of int *** from incompatible pointer typeint ** [-Wincompatible-pointer-types]int ***ipp p;^但是还是会编译通过执行结果是 num10, num2293564 p2293564, *p10, p2293560 ip2293560, *ip2293564, ip2293556 ipp2293560, *ipp2293564, ipp2293552指针 ipp 最终还是成功指向了指针 p虽然这样做是可行的但不建议这样去写。 如果按照正常的方式去编写这种指向指针的指针能最多能写多少个星号呢目前貌似没有找到相关资料来解释这个问题。就下面的案例来看能写到至少 6 颗星。 #includestdio.hint main() {int num 10;int *p num;int **ip p;int ***ipp ip;int ****ippp ipp;int *****ipppp ippp;int ******ippppp ipppp;printf(num%d, num%d\n, num, num);printf(p%d, *p%d, p%d\n, p, *p, p);printf(ip%d, *ip%d, ip%d\n, ip, *ip, ip);printf(ipp%d, *ipp%d, ipp%d\n, ipp, *ipp, ipp);printf(ippp%d, *ippp%d, ippp%d\n, ippp, *ippp, ippp);printf(ipppp%d, *ipppp%d, ipppp%d\n, ipppp, *ipppp, ipppp);printf(ippppp%d, *ippppp%d, ippppp%d\n, ippppp, *ippppp, ippppp);return 0; }执行结果 num10, num2293564 p2293564, *p10, p2293560 ip2293560, *ip2293564, ip2293556 ipp2293556, *ipp2293560, ipp2293552 ippp2293552, *ippp2293556, ippp2293548 ipppp2293548, *ipppp2293552, ipppp2293544 ippppp2293544, *ippppp2293548, ippppp2293540字符串 在 C 语言中字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。 因此一个以 null 结尾的字符串包含了组成字符串的字符。 字符 关键字 char 可以用来声明一个字符变量。char 变量既是一种整数最小的整数类型也是一种特殊的字符类型。如下所示 #includestdio.hint main() {char c A;printf(c%d, c%c, c, c);return 0; }执行结果 c65, cA可以发现字符 A 在输出的时候既可以使用数字格式输出也可以使用字符格式输出原因是它的数据类型是 char 类型是一种既是整型又是字符的数据类型。既然是整型那就能进行算数运算。如下例 #includestdio.hint main() {char c A 1;printf(c%d, c%c, c, c);return 0; }执行结果 c66, cB实际上char 能将字符 A 进行算数运算的原因是它的每个字符都有对应的无符号整型的数据与之对应。从这里的例子中可以知道字符 A 对应的整数是 65字符 B 对应的整数就是 66如果进行 Z - A 的运算能得到与整数 26对应的字符。 在计算机早期所有的数据在存储和运算时都要使用二进制数表示因为计算机用高电平和低电平分别表示1和0例如像a、b、c、d这样的52个字母包括大写以及0、1等数字还有一些常用的符号例如*、#、等在计算机中存储时也要使用二进制数来表示。 而具体用哪些二进制数字表示哪个符号的问题美国国家标准学会American National Standard Institute , ANSI制定了一套标准的单字节字符编码方案用于基于文本的数据。 它最初是美国国家标准供不同计算机在相互通信时用作共同遵守的西文字符编码标准后来它被国际标准化组织International Organization for Standardization, ISO定为国际标准称为ISO 646标准。适用于所有拉丁文字字母。 —— 摘自百度百科 这种字符和整数对应关系的标准被称之为 ASCII (American Standard Code for Information Interchange)即美国信息交换标准代码ASCII 码表的具体内容如下 二进制八进制十进制十六进制缩写/字符解释0000 0000000x00NUL(null)空字符0000 0001110x01SOH(start of headline)标题开始0000 0010220x02STX (start of text)正文开始0000 0011330x03ETX (end of text)正文结束0000 0100440x04EOT (end of transmission)传输结束0000 0101550x05ENQ (enquiry)请求0000 0110660x06ACK (acknowledge)收到通知0000 0111770x07BEL (bell)响铃0000 10001080x08BS (backspace)退格0000 10011190x09HT (horizontal tab)水平制表符0000 101012100x0ALF (NL line feed, new line)换行键0000 101113110x0BVT (vertical tab)垂直制表符0000 110014120x0CFF (NP form feed, new page)换页键0000 110115130x0DCR (carriage return)回车键0000 111016140x0ESO (shift out)不用切换0000 111117150x0FSI (shift in)启用切换0001 000020160x10DLE (data link escape)数据链路转义0001 000121170x11DC1 (device control 1)设备控制10001 001022180x12DC2 (device control 2)设备控制20001 001123190x13DC3 (device control 3)设备控制30001 010024200x14DC4 (device control 4)设备控制40001 010125210x15NAK (negative acknowledge)拒绝接收0001 011026220x16SYN (synchronous idle)同步空闲0001 011127230x17ETB (end of trans. block)结束传输块0001 100030240x18CAN (cancel)取消0001 100131250x19EM (end of medium)媒介结束0001 101032260x1ASUB (substitute)代替0001 101133270x1BESC (escape)换码(溢出)0001 110034280x1CFS (file separator)文件分隔符0001 110135290x1DGS (group separator)分组符0001 111036300x1ERS (record separator)记录分隔符0001 111137310x1FUS (unit separator)单元分隔符0010 000040320x20(space)空格0010 000141330x21!叹号0010 001042340x22双引号0010 001143350x23#井号0010 010044360x24$美元符0010 010145370x25%百分号0010 011046380x26和号0010 011147390x27’闭单引号0010 100050400x28(开括号0010 100151410x29)闭括号0010 101052420x2A*星号0010 101153430x2B加号0010 110054440x2C,逗号0010 110155450x2D-减号/破折号0010 111056460x2E.句号0010 111157470x2F/斜杠0011 000060480x300字符00011 000161490x311字符10011 001062500x322字符20011 001163510x333字符30011 010064520x344字符40011 010165530x355字符50011 011066540x366字符60011 011167550x377字符70011 100070560x388字符80011 100171570x399字符90011 101072580x3A:冒号0011 101173590x3B;分号0011 110074600x3C小于0011 110175610x3D等号0011 111076620x3E大于0011 111177630x3F?问号0100 0000100640x40电子邮件符号0100 0001101650x41A大写字母A0100 0010102660x42B大写字母B0100 0011103670x43C大写字母C0100 0100104680x44D大写字母D0100 0101105690x45E大写字母E0100 0110106700x46F大写字母F0100 0111107710x47G大写字母G0100 1000110720x48H大写字母H0100 1001111730x49I大写字母I1001010112740x4AJ大写字母J0100 1011113750x4BK大写字母K0100 1100114760x4CL大写字母L0100 1101115770x4DM大写字母M0100 1110116780x4EN大写字母N0100 1111117790x4FO大写字母O0101 0000120800x50P大写字母P0101 0001121810x51Q大写字母Q0101 0010122820x52R大写字母R0101 0011123830x53S大写字母S0101 0100124840x54T大写字母T0101 0101125850x55U大写字母U0101 0110126860x56V大写字母V0101 0111127870x57W大写字母W0101 1000130880x58X大写字母X0101 1001131890x59Y大写字母Y0101 1010132900x5AZ大写字母Z0101 1011133910x5B[开方括号0101 1100134920x5C\反斜杠0101 1101135930x5D]闭方括号0101 1110136940x5E^脱字符0101 1111137950x5F_下划线0110 0000140960x60开单引号0110 0001141970x61a小写字母a0110 0010142980x62b小写字母b0110 0011143990x63c小写字母c0110 01001441000x64d小写字母d0110 01011451010x65e小写字母e0110 01101461020x66f小写字母f0110 01111471030x67g小写字母g0110 10001501040x68h小写字母h0110 10011511050x69i小写字母i0110 10101521060x6Aj小写字母j0110 10111531070x6Bk小写字母k0110 11001541080x6Cl小写字母l0110 11011551090x6Dm小写字母m0110 11101561100x6En小写字母n0110 11111571110x6Fo小写字母o0111 00001601120x70p小写字母p0111 00011611130x71q小写字母q0111 00101621140x72r小写字母r0111 00111631150x73s小写字母s0111 01001641160x74t小写字母t0111 01011651170x75u小写字母u0111 01101661180x76v小写字母v0111 01111671190x77w小写字母w0111 10001701200x78x小写字母x0111 10011711210x79y小写字母y0111 10101721220x7Az小写字母z0111 10111731230x7B{开花括号0111 11001741240x7C|垂线0111 11011751250x7D}闭花括号0111 11101761260x7E~波浪号0111 11111771270x7FDEL (delete)删除 所以char 类型的数据对应的整型数据的取值范围应该在 [0, 127] 之间。使用循环可以将其全部输出并查看 #includestdio.hint main() {for(char c 0; c 128; c) {printf(c%d, c%c\n, c, c);if(c 0) {break;}}return 0; }因为 char 的最大值是 127, 如果当变量 c 是 127 的时候再执行自增操作则 c 的最高位会被进位为 1就被解释为负数了故而需要在循环体中做判断 c 0 就跳出循环的操作否则该循环将成为无限循环。 C 的字符串 很多资料在描述 C 的字符串的时候说在 C 中字符数组就是字符串其实不完全正确。根据定义字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。换句话说如果一个字符数组中的最后一个元素不是 NULL 字符那这个字符数组不能称之为字符串。示例如下 #includestdio.hint main() {char a[] {H, e, l, l, o};char b[] {H, e, l, l, o, \0};char c[] Hello;printf(sizeof(a) is %d, a%p, a%s\n, sizeof(a), a, a);printf(sizeof(b) is %d, b%p, b%s\n, sizeof(b), b, b);printf(sizeof(c) is %d, c%p, c%s\n, sizeof(c), c, c);return 0; }执行结果 sizeof(a) is 5, a0022FF3B, aHello sizeof(b) is 6, b0022FF35, bHello sizeof(c) is 6, c0022FF2F, cHello这里的变量 a, b, c 都是字符数组但变量 a 是不能被称之为是 C 的字符串的因为根据定义它的最后一个元素的不是 NULL 字符。 那为什么变量 c 能被称之为是字符串呢原因是在编写源程序的时候不需要把 null 字符放在字符串常量的末尾。编译器会在初始化数组时自动把 \0 放在字符串的末尾。也就是说C 在编译的时候将字符串字面量 Hello 分解成了一个以 NULL 字符结尾的字符数组。这也就是为什么使用 sizeof 运算变量 c 的时候得到的值是 6 的原因因为在字符 o 的后面还有一个 NULL 字符。可以简单的理解为 Hello 等价于 {H, e, l, l, o, \0}。 在之前以 指针 为主题的文章中说在 C 中数组变量就是一个特殊常量指针也称之为数组指针。那也就是说在上面的例子中变量 a, b, c 也都是指针。换句话说字符串变量也是一个指针变量。 同样的把之前数组指针的案例修改下更改数据类型为 char然后增加输出格式 %c。得到如下代码 #includestdio.h#define SIZE 6int main() {char cs[SIZE] {H, e, l, l, o, \0};char *p cs;// cs 和 p 都能使用运算符 *printf(*p%d, *cs%d, *cs%c\n, *p, *cs, *cs);printf( p%p, cs%p\n, p, cs);printf(p%p, cs%p\n, p, cs);// cs 和 p 都能使用逻辑运算符printf(p cs[1] is %s\n, p cs[1] ? true : false);printf(cs cs[1] is %s\n, cs cs[1] ? true : false);// cs 和 p 都能使用下标来操作元素for(int i 0; i SIZE; i) {printf(cs[%d]%d, cs[%d]%c, cs[%d]%p, p[%d]%d, p[%d]%p\n, i, cs[i], i, cs[i], i, cs[i], i, p[i], i, p[i]);}// cs 是常量指针故而不能做自运算while(p cs[SIZE - 1]) {p;// error: lvalue required as increment operand// cs;printf(p%p, *p%d, cs%p, *cs%d, *cs%c\n, p, *p, cs, *cs, *cs);}return 0; } 执行结果 *p72, *cs72, *csHp0022FF26, cs0022FF26 p0022FF20, cs0022FF26 p cs[1] is true cs cs[1] is true cs[0]72, cs[0]H, cs[0]0022FF26, p[0]72, p[0]0022FF26 cs[1]101, cs[1]e, cs[1]0022FF27, p[1]101, p[1]0022FF27 cs[2]108, cs[2]l, cs[2]0022FF28, p[2]108, p[2]0022FF28 cs[3]108, cs[3]l, cs[3]0022FF29, p[3]108, p[3]0022FF29 cs[4]111, cs[4]o, cs[4]0022FF2A, p[4]111, p[4]0022FF2A cs[5]0, cs[5] , cs[5]0022FF2B, p[5]0, p[5]0022FF2B p0022FF27, *p101, cs0022FF26, *cs72, *csH p0022FF28, *p108, cs0022FF26, *cs72, *csH p0022FF29, *p108, cs0022FF26, *cs72, *csH p0022FF2A, *p111, cs0022FF26, *cs72, *csH p0022FF2B, *p0, cs0022FF26, *cs72, *csH从这个简单的案例中证明了字符串变量也是一个指针变量。原因是因为数组是一个常量指针。那也就是说可以使用声明指针的形式来声明一个字符串。示例如下 #includestdio.hint main() {char *a Hello;printf(sizeof(a) is %d, a%p, a%s\n, sizeof(a), a, a);return 0; }执行结果 sizeof(a) is 4, a0022FF3C, aHello那如果声明两个相同的字符串指针变量他们的值相同吗代码如下 #includestdio.h #includestring.hint main() {char *a Hello;char *b Hello;if(a b) {printf(a b.\n);} else {printf(a ! b.\n);}printf(a%s, a%p, a[0]%p, *a%c, a%p\n, a, a, a[0], *a, a);printf(b%s, b%p, b[0]%p, *b%c, b%p\n, b, b, b[0], *b, b);return 0; }执行结果 a b. aHello, a00405044, a[0]00405044, *aH, a0022FF3C bHello, b00405044, b[0]00405044, *bH, b0022FF38从这个例子中可以知道的是两个字符指针指向的地址是同一个地址也就是说源程序中的两个字面量 Hello 在内存中使用同一个字符数组来进行存储的。换句话讲这个例子中的变量 a 和变量 b 不是两个长得一样而是它们就是同一个字符串。 综上可知字符串的特点有 字符串变量是使用 NULL 字符作为最后一个元素的一维字符数组字符串变量也是一个指针变量字符串指针指向的是字符数组的第一个元素的地址两个相同的字符串字面量在内存中使用同一个字符数组来进行存储 字符串输入与输出 如果想把一个字符串读取到程序中必须首先预留存储字符串的空间然后使用输入函数来获取这个字符串C 在 stdio.h 中提供了三个读取字符串的函数scanf、gets 和 fgets 。 scanf 函数 scanf 函数可以读取输入的信息到指定的地址值中配合变量的取值符可以更有效的读入数据 int age 1; scanf(%d, age); printf(age %d\n, age);如果是指针变量可以很容易的将地址传递给 scanf 函数如下 int *p; scanf(%d, p); printf(*p %d\n, *p);但是读入数据到指针指定位置的时候会覆盖所指向的数据并可能导致程序异常终止。 char *name hello; scanf(%s, name); printf(name %s\n, name);这个是 因为 scanf 把信息复制到由name指定的地址中而在这种情况下参数是个未被初始化的指针name可能指向任何地方。 gets 函数 gets 函数对于交互式程序非常方便它从系统的标准输入设备通常是键盘获得一个字符串。 因为字符串没有预定的长度所以 gets 函数通过判断遇到的第一个换行符 \n 结束输入按回车键可以产生这个字符。它读取换行符之前不包括换行符的所有字符并在这些字符后添加一个空字符\0。 char n[12]; char *p; p gets(n); printf(n%s\n, n); printf(p%p\n, p); printf(n%p\n, n);如果在 gets 函数在读取字符串时出错或者遇到文件结尾它就返回一个空(或0)地址这个空地址被称为空指针并且 stdio.h 里面定义的常量 NULL 来表示可以用下面的代码来进行一些错误检测。 char name[1024]; while(get(name) ! NULL) {// ... do something here .... }也可以通过 getchar 函数来完成上面的错误检测。 char ch; while((ch getchar()) ! EOF) {// ... do something ... }fgets 函数 fgets 函数也可以读入字符数据到变量中它和 gets 函数的区别主要有 fgets 需要第二个参数来说明最大读入字符数。如果这个参数值为 nfgets 就会读取最多 n-1 个字符或者读完一个换行符为止(因为会自动添加一个空字符(\n))由这两者中最先满足的那个结束输入。如果 fgets 读取到换行符就会把它存到字符串里而不是像 gets 那样丢弃。fgets 还需要第三个参数来说明读哪一个文件从键盘上读取数据时可以使用stdin(代表standard input)作为参数这个标识符在 stdio.h 中被定义。 #includestdio.h #define MAX 1024int main() {char n[MAX];char *p;printf(input:);p gets(n);printf(n%s\n, n);printf(p%p\n, p);printf(n%p\n, n);printf(input:);p fgets(n, MAX, stdin); printf(n%s\n, n);printf(p%p\n, p);printf(n%p\n, n);return 0; }执行结果 input:hello nhello p0061FB1C n0061FB1C input:hello nhellop0061FB1C n0061FB1C明显看到第二次输入的 hello 后面会带有一个回车符号。 scanf 和 gets 的区别 scanf 函数和 gets 函数的主要区别在于如何决定字符串何时结束。scanf 函数可以使用 %s 来读入一个单词而 gets 则是读入一个长字符串以回车键结束。如下所示代码 char n[1024]; char *p;printf(input:); p gets(n); printf(n%s\n, n);printf(input:); scanf(%s, n); printf(n%s\n, n);如果输入均为 hello world则输出结果如下 input:hello world nhello world input:hello world nhello这两种方法都是以遇到的第一个非空白字符开始的针对 scanf 函数的结束可以归纳为 如果使用 %s 格式字符串读取到但不包括下一个空白字符比如空格、制表符或换行符结束如果指定了字段宽度比如 %10sscanf 函数就会读取10个字符或者直到遇到第一个空白字符由二者最先满足的那一个终止输入 printf 函数 printf 函数接受一个格式控制字符串格式控制字符串是用双引号括起来的字符串包括三类信息 格式字符格式字符由 % 引导如 %d、%f等。它的作用是控制输出字符的格式。 转义字符格式控制字符串里的转义字符按照转义后的含义输出如换行符\n即输出回车。 普通字符普通字符即需要在输出时原样输出的字符如汉字或者其他普通单词。 格式字符说明d输出带符号的十进制整数正数的符号省略u以无符号的十进制整数形式输出o以无符号的八进制整数形式输出不输出前导符0x以无符号十六进制整数形式小写输出不输出前导符0xX以无符号十六进制整数形式大写输出不输出前导符0Xf以小数形式输出单、双精度数隐含输出6位小数e以指数形式小写e表示指数部分输出实数E以指数形式大写E表示指数部分输出实数g自动选取f或e中输出宽度较小的一种使用且不输出无意义的0c输出一个字符s输出字符串 puts 函数 puts 函数接受一个字符串参数的地址它遇到空字符\0就会结束输出所以必须要有空字符。puts 函数在显示字符串的时候会自动在其后添加一个换行符\n。 #includestdio.h #define STR learn C together!int main() {char cs[] defined by array!;char *cp defined by pointer!;puts(puts string!);puts(cs);puts(cp);puts(STR);puts(cs[3]);puts(cs4);puts(cp[3]);puts(cp4);return 0; }fputs 函数 fputs 函数 puts 函数面向文件版本两者主要的区别是 fputs 函数需要第二个参数来说明要写的文件可以使用 stdout (standard output) 作为参数来进行输出显示。与 puts 函数不同fputs 函数并不为输出自动添加换行符。 读取一行并把它回显在下一行用下面的两种循环都可以办到 // get puts {char line[81];while(gets(line))puts(line); } // fgets fputs {char line[81];while(fgets(line,81,stdin))fputs(line,stdout); }字符串函数 在 C 的标准库中提供了关于字符串的函数在使用之前需要引入头文件 string.h。关于这个头文件中包含的函数说明请参考http://www.cplusplus.com/reference/cstring/ strcpy 函数签名 char * strcpy ( char * destination, const char * source ); 函数描述将 source 所指的 C 字符串复制到 destination 所指的数组中包括终止的空字符并在该点停止。 名称来源 Copy string 使用示例 #includestdio.h #includestring.hint main() {char a[] Hello;char b[] World;// strcpy(a, b) 等价于 a bstrcpy(a, b);printf(a%s\n, a);printf(b%s\n, b);return 0; }执行结果 aWorld bWorld因为数组是一个常量指针所以它不能像普通变量那样直接将变量 b 的值赋予给变量 a。这里使用到 C 标准库中提供的 strcpy 函数将 b 的值赋予给 a。当然如果使用指针变量声明的字符串完成就可以不需要 strcpy 函数了。示例如下 #includestdio.h #includestring.hint main() {char *a Hello;char *b World;a b;printf(a%s\n, a);printf(b%s\n, b);return 0; } 执行结果 aWorld bWorld结果虽然一样但是使用指针的做法实际上就是将 b 的指针指向的地址值赋予了 a使得 a 的指向发生变化而已。 strcat 函数签名 char * strcat ( char * destination, const char * source ); 函数描述 将 source 字符串的副本追加到 destination 字符串。destination 中的终止空字符被 source 的第一个字符覆盖并且在由 destination 中的两个字符串联而成的新字符串的末尾包含一个空字符。 名称来源 Concatenate strings 使用示例 #includestdio.h #includestring.hint main() {char a[] Hello;char b[] World;strcat(strcat(a, ), b);printf(a%s\n, a);printf(b%s\n, b);return 0; }执行结果 aHello World bWorldstrlen 函数签名 size_t strlen ( const char * str ); 函数描述 返回 C 字符串 str 的长度。 名称来源 Get string length 使用示例 #includestdio.h #includestring.hint main() {char a[] Hello;char b[] World;printf(a%s, as length is %d\n, a, strlen(a));printf(b%s, bs length is %d\n, b, strlen(b));return 0; }执行结果 aHello, as length is 5 bWorld, bs length is 5strcmp 函数签名 int strcmp ( const char * str1, const char * str2 ); 函数描述 如果每个字符都一致则返回零否则比较第一个不同的字符的 ASCII 值。 名称来源 Compare two strings 使用示例 #includestdio.h #includestring.hint main() {char a[] Hello;char b[] World;int c strcmp(a, b);printf(strcmp(\%s\, \%s\) is %d\n, a, b, c);return 0; }执行结果 strcmp(Hello, World) is -1函数 strcmp 比较 Hello 和 World 的结果是 -1也就是 a b。那是因为 World 的第一个字符的 ASCII 值比 Hello 的大。如果将 World 更改为 Happy那就是比较第二个字符的 ASCII 值结果就是 a b 了。关于它的更多介绍可以参考 http://www.cplusplus.com/reference/cstring/strcmp/ strchr 函数签名 char * strchr ( const char * str, int character); 函数描述 返回指向 C 字符串 str 中 character 的第一个匹配项的指针。 名称来源 Locate first occurrence of character in string 使用示例 #includestdio.h #includestring.hint main() {char a[] Hello World!;char *c strchr(a, l);printf(c%p, c%c, c%d, c%s, c, *c, *c, c);return 0; }执行结果 c0022FF31, cl, c108, cllo World!返回的指针 c 指向的是原字符串的一个第一个 l 的字符可以使用指针运算统计出该字符串中某个字符的总数。如下例所示 #includestdio.h #includestring.hint main() {char a[] Hello World!;char *c a, ch l;int count 0;while(c) {c strchr(c, ch);if(!c) {// 如果找不到对应的字符则返回空指针break;}// printf(c%p, c%c, c%d, c%s\n, c, *c, *c, c);c;count;}printf(count of %c is %d.\n, ch, count);return 0; }执行结果 count of l is 3.strstr 函数签名 char * strstr ( char * str1, const char * str2 ); 函数描述 返回指向 str1 中第一个 str2 的指针如果 str2 不是 str1 的一部分则返回空指针。 使用示例 #includestdio.h #includestring.hint main() {char a[] The matching process does not include the terminating null-characters, but it stops there.;char *c strstr(a, null);if(c) {// c is not null pointerprintf(c%p, c%c, c%d, c%s\n, c, *c, *c, c);}return 0; }执行结果 c0022FF07, cn, c110, cnull-characters, but it stops there.C 的字符串 在 C 中字符串的声明和使用都比 C 中来得简单。示例代码如下 #includeiostream #includestring using namespace std;int main() {string str Hello World;cout strs length is str.size() endl;str !;cout strs length is str.size() endl;cout str endl;// 在字符串从左开始查找第一次出现指定字符串的位置并返回下标int index str.find(l);cout index is index endl;// 在字符串从右开始查找第一次出现指定字符串的位置并返回下标index str.rfind( );cout index is index endl;// 从指定下标开始截取字符串截取指定的长度string substring str.substr(index 1, 2);cout substring is substring endl;// 从指定下标开始截取字符串截取到末尾substring str.substr(index 1);cout substring is substring endl;substring str.replace(index 1, 5, C);cout substring is substring endl;return 0; }执行结果 strs length is 11 strs length is 12 Hello World! index is 2 index is 5 substring is Wo substring is World! substring is Hello C!枚举类型 枚举是 C 语言中的一种基本数据类型它可以让数据更简洁更易读。 使用 enum 关键字定义一系列枚举值表示星期例如 enum WEEK { MON, TUE, WED, THU, FRI, SAT, SUN };接下来就能在程序中使用这些枚举值表示星期了 #includestdio.henum WEEK { MON, TUE, WED, THU, FRI, SAT, SUN };int main() {enum WEEK w SAT;switch(w) {case MON:case TUE:case WED:case THU:case FRI:printf(today is week. );break;case SAT:case SUN:printf(today is weekend. );break;} return 0; }执行结果 today is weekend.非常简单如果尝试将其以数字的形式输出呢代码如下 #includestdio.henum WEEK { MON, TUE, WED, THU, FRI, SAT, SUN };int main() {enum WEEK w SAT;printf(today is %d. , w); return 0; }执行结果 today is 5.会发现输出的 5 正好与 SAT 在声明枚举的语句中所在的下标是一致的于是全部将其输出查看结果 printf(MON is %d.\n, MON); printf(TUE is %d.\n, TUE); printf(WED is %d.\n, WED); printf(THU is %d.\n, THU); printf(FRI is %d.\n, FRI); printf(SAT is %d.\n, SAT); printf(SUN is %d.\n, SUN); 其结果是 MON is 0. TUE is 1. WED is 2. THU is 3. FRI is 4. SAT is 5. SUN is 6.因为枚举值实际上就是一系列连续的数字默认从零开始。当然可以在定义的时候手动更改他们的值。例如 enum WEEK { MON, TUE, WED8, THU, FRI, SAT, SUN };输出对应的数字值分别是 MON is 0. TUE is 1. WED is 8. THU is 9. FRI is 10. SAT is 11. SUN is 12.当然故意将其设置为相同的值是被允许的但不建议这样做 反例1 enum WEEK { MON, TUE, WED8, THU8, FRI, SAT, SUN };反例2 enum WEEK { MON5, TUE, WED, THU3, FRI, SAT, SUN };这样写都会导致枚举值出现重复的数值也不利于程序的维护。 结构体 在C语言中结构体(struct)指的是一种数据结构是C语言中聚合数据类型(aggregate data type)的一类。 结构体可以被声明为变量、指针或数组等用以实现较复杂的数据结构。 结构体同时也是一些元素的集合这些元素称为结构体的成员(member)且这些成员可以为不同的类型成员一般用名字访问。 —— 摘自百度百科 声明与定义 在 C 语言提供的基本数据类型中只能表示单一的类型的数据类型如果需要将多个数据类型组合成为一个整体的作为新的类型的话就需要使用到结构体。 在表达式 int a 10;中变量 a 被定义为了 int 类型无论如何变量 a 都表示的是一个数。 而实际运用中需要依赖变量某个变量来表示更多的内容。 例如需要表示某个人的信息包括姓名年龄体重等。那每个信息可以使用一个基本数据类型来表示。如下代码所示 #includestdio.hint main() {char name[50] tom;unsigned int age 27;double weight 57.9;printf(name is %s, age is %d, weight is %f\n, name, age, weight);return 0; }执行结果 name is tom, age is 27, weight is 57.900000但是作为某个人的信息它们应该是一个整体。这时候可以使用结构体定义一个新的类型这个类型将包含需要表示的某个人的信息的基本类型。如下代码所示 /*** 人*/ struct Person {/*** 姓名*/char name[50];/*** 年龄*/unsigned int age;/*** 体重*/double weight; };这里定义了一个 Person 类型该类型包含了 C 提供的三个基本类型。而作为整体该类型将表示某个人的数据。 结构体的定义语法 struct 结构体名称 {member-list }定义好的结构体与基本数据类型的使用方式是一样的。如下所示 char name[50]; unsigned int age; double weight; struct Person man;或者可以在声明的时候为其赋予初始值 char name[50] Tom; unsigned int age 27; double weight 57.9; struct Person man {Mark, 28, 60.2};也可以在声明之后使用运算符 . 来为结构体变量赋值完整代码如下所示 #includestdio.h #includestring.h/*** 人*/ struct Person {/*** 姓名*/char name[50];/*** 年龄*/unsigned int age;/*** 体重*/double weight; };int main() {// 声明普通的变量char name[50];unsigned int age;double weight;// 为普通的变量赋值strcpy(name, Tom);age 27;weight 57.9;// 输出变量的值printf(name is %s, age is %d, weight is %f\n, name, age, weight);// 声明结构体变量struct Person man;// 为结构体变量的内部成员赋值strcpy(man.name, Mark);man.age 28;man.weight 60.2;// 输出结构体变量的内部成员的值printf(name is %s, age is %d, weight is %f\n, man.name, man.age, man.weight);return 0; }执行结果 name is Tom, age is 27, weight is 57.900000 name is Mark, age is 28, weight is 60.200000可以将结构体看作是将多个基础数据类型的变量进行打包的容器使得这些只能表示单一信息的单元组合成一个整体无论是取值还是储值都需要通过变量 man 使用运算符 . 来操作。在阅读代码的时候可以将运算符 . 理解为汉字 的比如 man.age 28 可以理解为 man 的 age 是 28 mans age is 28。也加强了代码的可读性。 当然结构体的内部成员除了基础数据类型之外还可以是指针变量枚举变量和其他结构体例如 /*** 性别*/ enum Gender {Girl, Boy};/*** 成绩*/ struct Score {/*** 语文成绩*/int Chinese;/*** 数学成绩*/int Math;/*** 英语成绩*/int English; }; /*** 人*/ struct Person {/*** 姓名*/char name[50];/*** 年龄*/unsigned int age;/*** 性别*/enum Gender gender;/*** 体重*/double weight;/*** 成绩*/struct Score score; };结构体作为参数 因为结构体类型中包含多个数据类型的成员以结构体作为函数参数能减少函数的参数列表的长度使得代码更加简洁。示例如下 #includestdio.henum Gender {Girl0, Boy1};/*** 人*/ struct Person {/*** 姓名*/char name[50];/*** 年龄*/unsigned int age;/*** 性别*/enum Gender gender;/*** 体重*/double weight; };/*** 打印 Person 结构体的成员变量*/ void print(struct Person person) {char *gender person.gender ? Boy : Girl;printf(Person{name:%s, age:%d, gender:%s, weight:%f}\n, person.name, person.age, gender, person.weight);}int main() {struct Person man {Mary, 16, Girl, 48.2};print(man);return 0; }执行结果 Person{name:Mary, age:16, gender:Boy, weight:48.200000}如果结构体中的成员变量较多使用这种方式传递数据相比较把所有的成员变量列举到形参列表中而言显得更加方便简洁。 指向结构体的指针 同样的声明一个指向结构体的指针也能作为函数参数进行数据的传递。但是作为指针变量需要访问结构体中的成员变量时需要使用 - 运算符。如下所示 #includestdio.henum Gender {Girl0, Boy1};/*** 人*/ struct Person {/*** 姓名*/char name[50];/*** 年龄*/unsigned int age;/*** 性别*/enum Gender gender;/*** 体重*/double weight; };/*** 打印 Person 结构体的成员变量*/ void print(struct Person *p) {char *gender p-gender ? Boy : Girl;printf(Person{name:%s, age:%d, gender:%s, weight:%f}\n, p-name, p-age, gender, p-weight); }int main() {struct Person man {Mary, 16, Girl, 48.2};struct Person *p man;printf(this persons name is %s\n, p-name);print(p);return 0; }执行结果 this persons name is Mary Person{name:Mary, age:16, gender:Girl, weight:48.200000}结构体占用的内存空间大小 使用 sizeof 能够量出某个变量或者类型的占用空间的字节数那结构体占用多大的内存空间呢首先来看一段代码 #includestdio.hint main() {printf(sizeof(char) is %d\n, sizeof(char));printf(sizeof(int) is %d\n, sizeof(int));printf(sizeof(double) is %d\n, sizeof(double));printf(\n);struct A{int a; int b;};printf(sizeof(A) is %d\n, sizeof(struct A));struct B{int a; double b;};printf(sizeof(B) is %d\n, sizeof(struct B));struct C{double a; double b;};printf(sizeof(C) is %d\n, sizeof(struct C));struct D{double a; double b; int c;};printf(sizeof(D) is %d\n, sizeof(struct D));struct E{double a; double b; int c; int d;};printf(sizeof(E) is %d\n, sizeof(struct E));struct F{double a; double b; int c; int d; char e;};printf(sizeof(F) is %d\n, sizeof(struct F));return 0; }执行结果 sizeof(char) is 1 sizeof(int) is 4 sizeof(double) is 8sizeof(A) is 8 sizeof(B) is 16 sizeof(C) is 16 sizeof(D) is 24 sizeof(E) is 24 sizeof(F) is 32结构体 A, C, E 的大小正好是所有的内部成员的 size 之和。而其他的结构体的 size 都比内部成员的 size 的和还要大些。由此可知结构体占用的内存空间大小不是简单的几个类型的 size 的和。 从结果上看结构体的 size 貌似是以 8 为单位扩张的。例如当成员中已经有两个 double 成员的时候再增加一个 int 成员就将自身扩张 8 个字节于是 size 为 24。 实际上结构体的 size 在扩张的时候是取决于 C 中的 #pragma pack(n) 指令。缺省情况下n 的值为8。这也就是该例中扩张 8 个字节的原因。因为 n 的合法的数值分别是1、2、4、8、16。若将该例中增加该指令并设置 n 的值为 1结构体就会以 1 位单位进行扩张。执行结果如下 sizeof(char) is 1 sizeof(int) is 4 sizeof(double) is 8sizeof(A) is 8 sizeof(B) is 12 sizeof(C) is 16 sizeof(D) is 20 sizeof(E) is 24 sizeof(F) is 25这样就能看到结构体的 size 就正好是各个成员的 size 之和了。 编译器中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况 如果n大于等于该变量所占用的字节数那么偏移量必须满足默认的对齐方式如果n小于该变量的类型所占用的字节数那么偏移量为n的倍数不用满足默认的对齐方式。 结构的总大小也有个约束条件分下面两种情况如果n大于所有成员变量类型所占用的字节数那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数否则必须为n的倍数。 —— 摘自百度百科 位域 除了使用指令能约束结构体的 size 之外使用位域也能约束某个成员变量占用的内存大小进而调整 结构体的 size。在结构内声明位域的形式如下 struct 结构体标识符 {type [member_name] : width ; };在使用位域的时候需要注意以下几个问题 成员类型只能为 int(整型)unsigned int(无符号整型)signed int(有符号整型) 三种类型决定了如何解释位域的值。位域中位的数量。宽度必须小于或等于指定类型的位宽度。 示例代码如下 #includestdio.hint main() {struct A { int a:1; int b:1; }; printf(sizeof(A) is %d\n, sizeof(struct A));return 0; }执行结果 sizeof(A) is 4因为结构体 A 内部成员使用的 int 型其 size 为 4也就是 32 个 bit 位。所以位域的宽度不能超过 int 的宽度也就是不能超过 32。在本例中两个 int 类型的位域宽度值都是 1 在实际存储中它只占用 1 个 bit 位所以结构体的 size 就是一个 int 类型的 size。换句话将结构体中所有 int 类型的成员的位域值之和不超过 32 的话结构体 A 的 size 就为 4。如下例所示 #includestdio.hint main() {struct A {int a0:1; int a1:1; int a2:1; int a3:1; int a4:1;int a5:1; int a6:1; int a7:1; int a8:1; int a9:1;int b0:1; int b1:1; int b2:1; int b3:1; int b4:1;int b5:1; int b6:1; int b7:1; int b8:1; int b9:1;int c0:1; int c1:1; int c2:1; int c3:1; int c4:1;int c5:1; int c6:1; int c7:1; int c8:1; int c9:1;int d0:1; int d1:1;};printf(sizeof(A) is %d\n, sizeof(struct A));return 0; }执行结果 sizeof(A) is 4该例中结构体 A 的成员变量有 32 个但是结构体 A 的 size 仍然是 4因为没有超出一个 int 的位宽。如果在增加一个相同的成员则超出了一个 int 的位宽其 size 就会扩张为 8。 结构体小案例 结构体能够表示很多场景的实体比如订单商品等。在程序中如果需要使用某个数据类型来表示一个订单信息或者一个商品信息。使用结构体是一个不错的选择。 下面是一个模拟了一个简单的订单实体的数据小案例。本例中声明了三个结构体一个枚举类型运用随机数并结合之前的文章的内容。具体代码如下所示 #includestdio.h #includestdlib.h #includetime.h #includestring.h// 定义存货量的上限值 #define INVENTORY_QUANTITY 9/*** 支付方式枚举*/ enum PayWay {Alipay, WeChatPay, ApplePay, UnionPay, CreditCard};/*** 商品详情*/ struct Goods {/*** 商品名称*/char name[50];/*** 商品价格*/double price;/*** 商品折扣*/unsigned short int discount; };/*** 订单详情*/ struct OrderDetail {/*** 该商品购买的数量*/int quantity;/*** 小计*/double subtotalAmount;/*** 该商品的详细信息*/struct Goods goods; };/*** 订单*/ struct Order {/*** 总计*/double totalAmount;/*** 订单支付方式*/enum PayWay payway;/*** 订单详情*/struct OrderDetail details[100]; };int main() {// 定义多个商品集合用于后面模拟存货struct Goods stocks[INVENTORY_QUANTITY] {{键盘, 199.0, 9},{鼠标, 129.5, 8},{电源, 109.0, 10},{音响, 699.0, 9},{耳机, 169.0, 10},{插板, 19.0, 10},{电脑, 7899.0, 10},{手机, 4999.0, 10},{平板, 299.0, 10}}; // 使用随机数模拟购物需要的数据int count 0;int indexs[INVENTORY_QUANTITY];struct Order order {0.0, Alipay};{// 初始化已购清单的索引for(int k 0; k INVENTORY_QUANTITY; k) {indexs[k] -1;}// 使用当前时间作来初始化随机函数种子srand((unsigned)time(NULL));// 随机一个数字这个数字不能超过 stocks 的长度count rand() % INVENTORY_QUANTITY;for(int i 0; i count; i) {// 随机一个商品int index -1, exist 0;while (index 0) {index rand() % INVENTORY_QUANTITY;for(int j 0; j INVENTORY_QUANTITY; j) {if(indexs[j] index) {exist 1;break;}}if(exist 1) {// 已经在买过该商品, 重新选择新的下标exist 0;index -1;}}for(int k 0; k INVENTORY_QUANTITY; k) {if(indexs[k] 0) {indexs[k] index;break;}}struct Goods goods stocks[index];// 随机一个数量表示购买该商品的数量int quantity rand() % 50;// 计算小计double subtotalAmount quantity * goods.price * goods.discount / 10.0;order.totalAmount subtotalAmount;// 生成一个订单详情信息struct OrderDetail detail {quantity, subtotalAmount, goods};order.details[i] detail;}}// 打印订单信息if(count 0) {printf(\n\n);{char way[11] ;switch(order.payway) {case Alipay: strcpy(way, 支付宝); break;case WeChatPay: strcpy(way, 微信支付); break;case ApplePay: strcpy(way, 苹果支付); break;case UnionPay: strcpy(way, 银联支付); break;case CreditCard: strcpy(way, 信用卡支付); break;}printf(订单总额%f, 支付方式:%s, order.totalAmount, way);}printf(\n----------------------------------------------------------\n);printf(序号 \t 商品 \t 数量 \t 单价 \t\t 小计.\n);for(int i 0; i count; i) {struct OrderDetail detail order.details[i];char *name detail.goods.name;int quantity detail.quantity;double price detail.goods.price;int discount detail.goods.discount;double subtotalAmount detail.subtotalAmount;printf( %d. \t %s \t %d \t %.2f \t %.2f\n, (i 1), name, quantity, price, subtotalAmount);if(discount 0 discount 10) {double subPrice price * discount / 10.0;printf( \t \t \t 折后价 - %.2f\n, subPrice);}}printf(\n\n);} else {printf(没有查询到有效的订单信息);}return 0; }执行结果 订单总额53539.800000, 支付方式:支付宝 ---------------------------------------------------------- 序号 商品 数量 单价 小计.1. 鼠标 44 129.50 4558.40折后价 - 103.602. 音响 25 699.00 15727.50折后价 - 629.103. 键盘 19 199.00 3402.90折后价 - 179.104. 平板 15 299.00 4485.005. 插板 13 19.00 247.006. 电源 47 109.00 5123.007. 手机 4 4999.00 19996.00 联合体 在进行某些算法的C语言编程的时候需要使几种不同类型的变量存放到同一段内存单元中。 也就是使用覆盖技术让几个变量互相覆盖。这种几个不同的变量共同占用一段内存的结构在C语言中被称作“共用体”类型结构简称共用体也叫联合体。 —— 摘自百度百科 定义联合体与使用 联合体是一种特殊的数据类型允许在相同的内存位置存储不同的数据类型。程序中可以定义带有多个成员的联合体但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。用到的关键字是 union其格式如下 union 联合体名称 {member-list }实例代码如下 #includestdio.h #includestring.hunion Person {char name[50];unsigned int age;double weight; };int main() {union Person person;strcpy(person.name, tom);person.age 27;person.weight 46.9;printf(person.name %s, person.age %d, person.weight %f\n, person.name, person.age, person.weight);return 0; }执行结果 person.name 33333sG諏鱱AQ, person.age 858993459, person.weight 46.900000可以看到只有成员 weight 的值是被正确的输出原因是这三个成员变量在储值的时候占用的是同一个内存空间相当于这三个成员指向的是同一块内存空间当 weight 被赋值之后其他的成员的值就被损坏。使用 可以查看各个成员指向的地址值 union Person up; printf(up%p, up.name%p, up.age%p, up.weight%p\n, up, up.name, up.age, up.weight);执行结果如下 up0022FF08, up.name0022FF08, up.age0022FF08, up.weight0022FF08如果需要在联合体变量被声明的时候赋予初始值的话因为它们共用同一个内存空间故而不需要将每个值都赋值而且编译器只会接收第一个值作为有效值填入合适的成员中。例如 #includestdio.h #includestring.hunion Person {char name[50];unsigned int age;double weight; };int main() {union Person up1 {Tom, 12, 4.6};union Person up2 {12};union Person up3 {4.6};union Person up4 {Tom};printf(name%s, age%d, weight%f\n, up1.name, up1.age, up1.weight);printf(name%s, age%d, weight%f\n, up2.name, up2.age, up2.weight);printf(name%s, age%d, weight%f\n, up3.name, up3.age, up3.weight);printf(name%s, age%d, weight%f\n, up4.name, up4.age, up4.weight);return 0; }该源程序在编译的时候给出警告和提示 warning: excess elements in union initializerunion Person up1 {Tom, 12, 4.6};^~ note: (near initialization for up1) warning: excess elements in union initializerunion Person up1 {Tom, 12, 4.6};^~~ note: (near initialization for up1)执行结果变成了 nameTom, age7171924, weight0.000000 name, age12, weight0.000000 name, age4, weight0.000000 nameTom, age7171924, weight0.000000从本例中可以看到联合体 up1 和 up4 接受的值是字符串 Tom联合体 up2 接受的值是 12联合体 up3 将传递的小数直接转换为了整型赋值给了 age 成员接受的值是 4。故而在给联合体变量声明的时候不需要为每个成员变量赋值只需要赋予一个合适类型的值即可。 联合体作为参数 将联合体作为参数传递到函数中的操作方式与结构体一样。但是因为共享内存的原因在函数内仅接受一个有效值。代码如下 #includestdio.h #includestring.hunion Person {char name[50];unsigned int age;double weight; };void printPerson(union Person p) {printf(Person{name:%s, age:%d, weight:%f}, p.name, p.age, p.weight); }int main() {union Person up {12};printPerson(up);return 0; }执行结果 Person{name:, age:12, weight:0.000000}指向联合体的指针 指向联合体的指针在使用上与结构体一致主要使用的运算符是 -。具体代码如下 #includestdio.h #includestring.hunion Person {char name[50];unsigned int age;double weight; };void printPerson(union Person *p) {printf(Person{name:%s, age:%d, weight:%f}, p-name, p-age, p-weight); }int main() {union Person up {12};union Person *p up;printPerson(p); return 0; }执行结果 Person{name:, age:12, weight:0.000000}位运算 位和字节 1字节byte 8 比特bit 字长指的是 CPU 一次性能够运算的数据的位数不同的计算机可能不一样。 一个英文字符和英文标点占用一个字节一个中文字符和中文标点占用两个字节。 计算机中的位 二进制数系统中每个0或1就是一个位(bit)位是数据存储的最小单位。其中8 bit就称为一个字节Byte。计算机中的CPU位数指的是CPU一次能处理的最大位数例如32位计算机的CPU一次最多能处理32位数据计算机中的CPU位数也成为机器字长和数据总线CPU与内部存储器之间连接的用于传输数据的线的根数的概念是统一的。 比特 计算机专业术语是信息量单位是由英文 bit 音译而来。二进制数的一位所包含的信息就是 1bit如二进制数 0101 就是 4bit。二进制数字中的位信息量的度量单位为信息量的最小单位。数字化音响中用电脉冲表达音频信号1 代表有脉冲0 代表脉冲间隔。如果波形上每个点的信息用四位一组的代码表示则称4比特比特数越高表达模拟信号就越精确对音频信号信号还原能力越强。 进制数 在实际开发中我们可以用0和1的字符串来表达信息例如某设备有八个传感器每个传感器的状态用1表示正常用0表示故障用一个二进制的字符串表示它们如01111011用一个字符或整数表示它就是123。 十进制转二进制 把十进数除以2记下余数余数保存在字符串中现用商除以2再记下余数如此循环直到商为0。把保存余数的字符串反过来就是结果。 例如123转化成二进制 123 ÷ 2 61 ... 1 61 ÷ 2 30 ... 1 30 ÷ 2 15 ... 0 15 ÷ 2 7 ... 1 7 ÷ 2 3 ... 1 3 ÷ 2 1 ... 1 1 ÷ 1 0 ... 1于是十进制的 123 的用二进制表示就是1111011代码实现如下 #includestdio.h #includestdlib.h// 十进制转换为二进制辗转相除法 void dec2bin(int decimal, char * bstr) {int quotient decimal;char cs[32];int i -1;// 初始化 cs 全部使用 \0 填充for(int j 0; j 32;j) {cs[j] \0;}do {// div 函数来自 stdlib.h, 可以通过该函数得到商quotient和余数remainderdiv_t r div(quotient, 2);quotient r.quot;cs[i] (char)(48 r.rem);} while(quotient 0);// 反转 cs 中的字符得到二进制的字符串表达形式for(int j 0; j i;j) {char c cs[i-j];bstr[j]c;} }int main() {int n 123;char bstr[32];dec2bin(n, bstr);printf(%d -- %s\n, n, bstr);return 0; }二进制转十进制 把二进制数从右往左依次与2的次幂数相乘最右边的为0次幂往左次幂依次升高将每位的乘积相加即可 例如1111011转化成十进制 1*2^6 1*2^5 1*2^4 1*2^3 0*2^2 1*2^1 1*2^0结果是123。代码实现如下 #includestdio.h #includestring.h #includemath.h// 二进制转换为十进制 int bin2dec(char * bstr) {int len strlen(bstr);int sum 0;for(int i 0; i len; i) {char c bstr[len-i-1];int b (int)c - 48;sum b * exp2(i);}return sum; }int main() {char bstr[] 1111011;int n bin2dec(bstr);printf(%s -- %d\n, bstr, n);return 0; }八进制 一种以8为基数的计数法采用01234567八个数字逢八进1。 一些编程语言中常常以数字0开始表明该数字是八进制。 八进制的数和二进制数可以按位对应八进制一位对应二进制三位因此常应用在计算机语言中。 十六进制 在数学中是一种逢16进1的进位制。一般用数字0到9和字母A到F或af表示其中:AF表示10~15。 位逻辑运算符 位运算的运算分量只能是整型或字符型数据。 位运算把运算对象看作是由二进位组成的位串信息按位完成指定的运算得到位串信息的结果。 位运算符有 (按位与)、|(按位或)、^(按位异或)、~ (按位取反)。 优先级从高到低依次为~、、^、| 可以将位运算中的 1 和 0 理解为是逻辑中的真和假或者为电路中的闭路和开路。 与运算 同真为真存假则假 printf((1 1) %d\n, (1 1)); // 1 printf((1 0) %d\n, (1 0)); // 0 printf((0 1) %d\n, (0 1)); // 0 printf((0 0) %d\n, (0 0)); // 0或运算| 同假为假存真为真 printf((1 | 1) %d\n, (1 | 1)); // 1 printf((1 | 0) %d\n, (1 | 0)); // 1 printf((0 | 1) %d\n, (0 | 1)); // 1 printf((0 | 0) %d\n, (0 | 0)); // 0非运算~ 真即是假假即是真 异或运算^ 相同为假相异为真 printf((1 ^ 1) %d\n, (1 ^ 1)); // 0 printf((1 ^ 0) %d\n, (1 ^ 0)); // 1 printf((0 ^ 1) %d\n, (0 ^ 1)); // 1 printf((0 ^ 0) %d\n, (0 ^ 0)); // 0位移运算 或 左移运算符 左移运算符是用来将一个数的各二进制位左移若干位移动的位数由右操作数指定右操作数必须是非负值其右边空出的位用0填补高位左移溢出则舍弃该高位。 // 128 1 ⇔ 256 × 2^1 printf((128 1) %d\n, (128 1)); // 256 // 64 2 ⇔ 64 × 2^2 printf((64 2) %d\n, (64 2)); // 256 // 32 3 ⇔ 32 × 2^3 printf((32 3) %d\n, (32 3)); // 256右移运算符 右移运算符是用来将一个数的各二进制位右移若干位移动的位数由右操作数指定右操作数必须是非负值移到右端的低位被舍弃对于无符号数高位补0。对于有符号数某些机器将对左边空出的部分用符号位填补即“算术移位”而另一些机器则对左边空出的部分用0填补即“逻辑移位”。 注意 对于无符号数右移时左边高位移入0 对于有符号的值 如果原来符号位为 0即正数则左边也是移入0如果符号位原来为 1即负数则左边移入0还是1要取决于所用的计算机系统 移入0的称为“逻辑移位”,即简单移位移入1的称为“算术移位”。 // 256 1 ⇔ 256 ÷ 2^1 printf((256 1) %d\n, (256 1)); // 128 // 256 2 ⇔ 256 ÷ 2^2 printf((256 2) %d\n, (256 2)); // 64 // 256 3 ⇔ 256 ÷ 2^3 printf((256 3) %d\n, (256 3)); // 32位运算赋值运算符 , |, , , ∧ int a 256; printf(a %d\n, a); a 1; printf(a %d\n, a); a 1; printf(a %d\n, a); a 1; printf(a %d\n, a); a 1; printf(a %d\n, a); a 1; printf(a %d\n, a); a 1; printf(a %d\n, a);预处理器 预处理器是在真正的编译开始之前由编译器调用的独立程序。预处理器可以删除注释、包含其他文件以及执行宏宏macro是一段重复文字的简短描写替代。 #include 包含一个源代码文件例如 #include stdio.h#define 定义宏例如 #define IDC_STATIC -1#ifndef #ifdef #endif #ifndef 与 #endif 联合使用表示如果某个宏未定义则执行相应的逻辑。例如 #ifndef IDC_STATIC#define IDC_STATIC -1 #endif#ifdef 与 #endif 联合使用表示如果某个宏已定义则执行相应的逻辑。例如 #ifdef IDC_STATIC// do something here. #endif#if #elif #else 预处理器中的条件结构示例代码 #if IDC_STATIC 0#define IDC_STATIC -1 #elif !defined(MSG)#define MSG MSG is undefined! #elif defined(MSG)#define MSG MSG is defined! #else#define MSG invalid code is here #endif#undef 取消已定义的宏例如 #ifdef IDC_STATIC#undef IDC_STATIC #endif文件 C 的标输出库 stdio 中提供了对文件进行读写的函数。 打开文件 函数签名FILE * fopen ( const char * filename, const char * mode ); 返回说明如果成功打开该文件则返回指向该文件的 FILE 对象的指针 参数说明 filename需要打开的文件名mode打开文件的模式 关于参数 mode 的取值 值说明rreadwwriteaappendrread/updatewwrite/updateaappend/update 详细说明请参考http://www.cplusplus.com/reference/cstdio/fopen/ 关闭文件 函数签名int fclose ( FILE * stream ); 返回说明如果成功关闭该文件则返回 0 参数说明 stream指向该文件的 FILE 对象的指针 详细说明请参考http://www.cplusplus.com/reference/cstdio/fclose/ 写入文件 函数签名int fputs ( const char * str, FILE * stream ); 返回说明成功时返回非负值 参数说明 str需要写入的字符串常量stream指向该文件的 FILE 对象的指针 详细说明请参考http://www.cplusplus.com/reference/cstdio/fputs/ 读取文件 函数签名char * fgets ( char * str, int num, FILE * stream ); 返回说明成功时传入的参数 str 的值 参数说明 str指向在其中复制字符串读取的字符数组的指针num要复制到str中的最大字符数包括终止的空字符stream指向该文件的 FILE 对象的指针 详细说明请参考http://www.cplusplus.com/reference/cstdio/fgets/ 简单示例 #includestdio.hint main() {const char *filename /io.txt;// 写{FILE *f fopen(/io.txt, w);int r fputs(Hello World.\n —— marvelousness, f);if(r -1) {r fclose(f);if(r 0) {// printf(写入完成.\n);}}}// 读{FILE *f fopen(/io.txt, r);char buff[1024];while(fgets(buff, 1024, f) ! NULL) {printf(%s, buff);}int r fclose(f);}return 0; }执行结果 Hello World.—— marvelousness内存管理 了解变量的存储类别以及动态内存分配的知识 存储类别 C语言中的四种存储类别 自动变量 auto、静态变量static、寄存器register、外部变量 extern。 自动变量 auto 通常在自定义函数内或代码段中用 {} 括起来的定义的变量都是自动变量除了加了static关键字修饰的变量也称为局部变量。 自动变量都是动态地分配存储空间的数据存储在动态存储区中。 函数中的形参和在函数中定义的变量包括在复合语句中定义的变量都属于这个分类在调用该函数时系统会给它们分配存储空间在函数调用结束时就自动释放这些存储空间。 自动变量用关键字 auto 进行存储类别的声明例如声明一个自动变量: void func() {auto int a 10; }外部变量 外部变量即全局变量是在函数的外部定义的它的作用域为从变量定义处开始到本程序文件的末尾。 int a 10;int main() {return 0; }或者通过 extern 指定某个全局变量来自外部文件例如在 other.c 中定义的全局变量在 app.c 中使用 第一个源文件 other.c 静态变量static 有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值这时就应该指定局部变量为静态局部变量用关键字 static 进行声明。 #includestdio.hvoid func(int i) {static int a0;a i;printf(a%d\n, a); }int main() {func(1);func(1);func(1);return 0; }执行结果 a1 a2 a3如果将程序中的 static 去掉则输出的结果将会恒为 a1 。 寄存器变量register 为提高效率C 语言允许将局部变量的值存放在 CPU 的寄存器中这种变量叫做寄存器变量。 用关键字 register 声明。 使用寄存器变量需要注意以下几点: 只有局部自动变量和形式参数可以作为寄存器变量。一个计算机系统中的寄存器数目有限不能定义任意多个寄存器变量。不能使用取地址运算符 求寄存器变量的地址。 void func(register int i) {register int a 10;printf(a%d,i%d\n, a, i); }相关概念 了解变量的作用域、链接、存出期 作用域(scope) 作用域描述了程序中合法访问一个标识符的区域。 一个C变量的作用域可以是 代码块作用域(block scope)函数原型作用域(function prototype scope)文件作用域(file scope) 链接(linkage) 跟作用域类似变量的链接是一个空间概念描述了程序中合法访问一个标识符的区域。 一个C变量的链接类型可以是 外部链接(external linkage)内部链接(internal linkage)空链接(no linkage) 存储期(storage duration) 变量的声明周期描述了一个C变量在程序执行期间的存在时间。 一个C变量的存储期可以是 静态存储期(static storage duration)自动存储期(automatic storage duration)动态存储期(dynamic storage duration) 存储类别小结 存储类时期作用域链接声明方式自动自动代码块空代码块内寄存器自动代码块空代码块内 Register具有外部链接的静态静态文件外部所有函数之外具有内部链接的静态静态文件内部所有函数之外 static空链接的静态静态代码块空代码块之内 static 内存动态管理 C 语言为内存的分配和管理提供了几个函数。这些函数可以在 stdlib.h头文件中找到。 栈上开辟空间 默认情况下C 语言会根据基础类型为变量在栈上开辟空间。 int num 20;//在栈空间上开辟四个字节 char ns[4] {1, 2, 3, 4};//在栈空间上开辟10个字节的连续空间特点如下 空间开辟大小是固定的。数组在声明的时候必须指定数组的长度它所需要的内存在编译时分配。 动态内存函数 malloc 用来开辟动态内存 void * malloc(size_t size);这个函数向内存申请一块连续可用的空间并返回指向这块空间的指针。 如果开辟成功则返回一个指向开辟好空间的指针。如果开辟失败则返回一个NULL指针因此malloc的返回值一定要做检查。返回值的类型是 void* 所以 malloc 函数并不知道开辟空间的类型在使用的时候使用者需要手动转换如果参数 size 为 0malloc 的行为是标准是未定义的取决于编译器。 int n 26; // 开辟一块内存占 n 个 char 类型的大小 char *p malloc(n * sizeof(char)); for(int i 0; i n; i) {p[i] (char)(i 97); } printf(p%s\n, p); free(p);free 函数用来释放动态开辟的内存 void free (void* ptr);如果参数 ptr 指向的空间不是动态开辟的那free函数的行为是未定义的。如果参数 ptr 是NULL指针则函数什么事都不做。free前后指向的地址不发生任何变化改变的只是指针和对应的内存的管理关系。 int num 20; int* p num; printf(p%p\n, p); free(p); if(p NULL) {printf(p is NULL); } else {printf(p%p\n, p); } p NULL;// 一般再添加一句代码令指针为NULL。calloc 函数也用来动态内存分配 void* calloc (size_t num, size_t size);函数的功能是为 num 个大小为 size 的元素开辟一块空间并且把空间的每个字节初始化为0。与函数 malloc 的区别只在于 calloc 会在返回地址之前把申请的空间的每个字节初始化为全0。 int n 26; // 开辟 n 块内存每块内存占 char 类型的大小 char *p calloc(n, sizeof(char)); for(int i 0; i n; i) {p[i] (char)(i 65); } printf(p%s\n, p); free(p); // 释放指针realloc 函数的出现让动态内存管理更加灵活。 void * realloc(void * ptr, size_t size);ptr 是要调整的内存地址size 调整之后新大小返回值为调整之后的内存起始位置。这个函数调整原内存空间大小的基础上还会将原来内存中的数据移动到 新的空间。realloc 在调整内存空间的是存在两种情况 情况1原有空间之后有足够大的空间。这种情况下要扩展内存就在原有内存之后直接追加空间原来空间的数据不发生变化。情况2 原有空间之后没有足够多的空间。这种情况下在堆空间上另找一个合适大小的连续空间来使用。这样函数返回的是一个新的内存地址。 #includestdio.h #includestdlib.h #define SIZE_A 50 #define SIZE_B 100int main() {int *ptr malloc(SIZE_A);if(ptr NULL) {printf(动态分配内存失败\n);exit(0);}printf(ptr%p\n, ptr);int *p realloc(ptr, SIZE_B);if(p NULL) {printf(重新分配内存失败\n);exit(0);}printf( p %p\n, p);ptr p;free(ptr);free(p);return 0; }在上述源代码中当 SIZE_B 为 100在重新分配内存的时候原有空间之后没有足够的空间符合情况2会返回一个新的地址修改 SIZE_B 为4在重新分配内存的时候原有空间有足够的空间符合情况1会返回原指针的地址。 常见动态内存错误 列举几个常见的错误 对NULL指针的解引用操作 *p 20; //如果p的值是NULL就会有问题对动态开辟空间的越界访问 int *p (int *)malloc(5*sizeof(int)); if(p ! NULL) {for(int i0; i5; i) {*(pi) i;//当i是5的时候越界访问} }对非动态开辟内存使用free释放 int a 10; int *p a; free(p);// p 的地址来自变量 a, 但 a 的内存并非动态开辟, 这种用法是错误的使用free释放一块动态开辟内存的一部分 int *p (int *)malloc(100); p; free(p);// p不再指向动态内存的起始位置对同一块动态内存多次释放 int *p (int *)malloc(100); free(p); free(p);//重复释放动态开辟内存忘记释放内存泄漏 #includestdio.h #includestdlib.hint main() {int *p (int *)malloc(100);if(NULL ! p) {*p 20;// free(p); // 在使用完指针后不要记得将其 free}return 0; }标准函数库 数学库 在 math.h 中提供了关于数学相关的函数 三角函数Trigonometric functions 数学中的自然常数 π 的值作为常量使用 M_PI 表示 printf(sin(30°)%.1f\n, sin(M_PI/6)); printf(cos(60°)%.1f\n, cos(M_PI/3)); printf(tan(45°)%.1f\n, tan(M_PI/4));printf(asin(0.5)%.0f°\n, asin(0.5) * (180/M_PI)); printf(acos(0.5)%.0f°\n, acos(0.5) * (180/M_PI)); printf(atan(1)%.0f°\n, atan(1) * (180/M_PI));指数函数Exponential functions exp 用来求以自然常数 e 的常数的指数结果。exp2 用来求以 2 的常数的指数结果。 printf(e^(0)%.3f\n, exp(0)); printf(e^(1)%.3f\n, exp(1)); printf(e^(2)%.3f\n, exp(2));printf(exp2(2)%f\n, exp2(2)); printf(exp2(3 )%f\n, exp2(3));对数函数Logarithmic functions 数学中的自然常数 e 的值在 C 语言中使用 M_E 表示 // 求以 e 为底数的对数 printf(ln(e)%f\n, log(M_E)); printf(ln(1)%f\n, log(1));// 求以 10 为底数的对数 printf(log10(10)%f\n, log10(10)); printf(log10(1)%f\n, log10(1));// 求以 2 为底数的对数 printf(log2(2)%f\n, log2(2)); printf(log2(1)%f\n, log2(1));幂函数Power functions printf(7^2 %f\n, pow(7.0, 2.0));平方根square root printf( 4 的平方根是 %f\n, sqrt(4.0));立方根cube root printf( 8 的立方根是 %f\n, cbrt(8.0));求斜边hypotenuse 已知直角三角形的两个直角边长度求斜边长度 printf(直角边为 3.0 和 4.0, 斜边为 %.1f\n, hypot(3.0, 4.0));取整函数 // 向上取整 printf( ceil(2.3)%.1f\n, ceil(2.3)); printf( ceil(2.6)%.1f\n, ceil(2.6)); // 向下取整 printf( floor(2.3)%.1f\n, floor(2.3)); printf( floor(2.6)%.1f\n, floor(2.6)); // 四舍五入 printf( round(2.3)%.1f\n, round(2.3)); printf( round(2.6)%.1f\n, round(2.6)); // 取余 printf( fmod(5.0, 3.0) %.1f\n, fmod(5.0,3.0)); printf( 5 mod 3 %.0f\n, 5%3);绝对值和最值 printf( |3.14| %.2f\n, fabs(3.14)); printf( |-3.14| %.2f\n, fabs(-3.14)); printf( fmax(3.14, 2.72) %.2f\n, fmax(3.14, 2.72)); printf( fmin(3.14, 2.72) %.2f\n, fmin(3.14, 2.72));通用工具库 在 stdlib.h 中提供了大量的工具库可以在开发过程中去使用。 double atof(const char *str) 把参数 str 所指向的字符串转换为一个浮点数类型为 double 型。 double r atof(3.14); // 结果是 3.14 r atof(3.14ok); // 结果是 3.14 r atof(3.14 ok); // 结果是 3.14int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数类型为 int 型。 int r atoi(2024); // 结果是 2024 r atoi(2024ok); // 结果是 2024 r atoi(2024 ok); // 结果是 2024long int atol(const char *str) 把参数 str 所指向的字符串转换为一个长整数类型为 long int 型 long int r atol(2024); // 结果是 2024 r atol(2024ok); // 结果是 2024 r atol(2024 ok); // 结果是 2024double strtod(const char *str, char *endptr)) 把参数 str 所指向的字符串转换为一个浮点数类型为 double 型 str要转换为双精度浮点数的字符串。endptr对类型为 char* 的对象的引用其值由函数设置为 str 中数值后的下一个字符。 这个当第二个参数为空时和 double atof(const char *str) 是相同但是当而第二形参引用后第二个指针会指向存储字符串位置的地址。 char str[] 23.67km; char *pstr; double ret strtod(str, pstr); printf(ret%f, pstr%s, ret, pstr);ret 的值是 23.67, pstr 的值是 km。 long int strtol(const char *str, char *endptr, int base) 把参数 str 所指向的字符串转换为一个有符号长整数类型为 long int 型。 str要转换为长整数的字符串。endptr对类型为 char* 的对象的引用其值由函数设置为 str 中数值后的下一个字符。base基数必须介于 2 和 36包含之间或者是特殊值 0。这个基数就表示这个多少数字就多少进制 char str[] 2024year; char *pstr; long int ret strtol(str, pstr, 8); printf(ret%d, pstr%s\n, ret, pstr);// ret 的值是 1044, pstr 的值是 year ret strtol(str, pstr, 10); printf(ret%d, pstr%s\n, ret, pstr);// ret 的值是 2024, pstr 的值是 yearunsigned long int strtoul(const char *str, char **endptr, int base) 把参数 str 所指向的字符串转换为一个无符号长整数类型为 unsigned long int 型。 char str[] 2024year; char *pstr; unsigned long int ret strtoul(str, pstr, 8); printf(ret%d, pstr%s\n, ret, pstr); ret strtol(str, pstr, 10); printf(ret%d, pstr%s\n, ret, pstr);void *calloc(size_t nitems, size_t size) 分配所需的内存空间并返回一个指向它的指针。 malloc 和 calloc 之间的不同点是malloc 不会设置内存为零。 // 参考前文【内存管理】章节的代码void free(void *ptr) 释放指针所指内存空间的数据该函数不返回任何值 // 参考前文【内存管理】章节的代码void *malloc(size_t size) 分配所需的内存空间并返回一个指向它的指针。 // 参考前文【内存管理】章节的代码void *realloc(void *ptr, size_t size) 尝试重新调整之前调用 malloc 或 calloc 所分配的 ptr 所指向的内存块的大小。 该函数返回一个指针 指向重新分配大小的内存。如果请求失败则返回 NULL。 // 参考前文内存管理章节的代码void abort(void) 使一个异常程序终止。 for(int i 0; i 100; i) {if(i 30) {abort();终止程序}printf(i%d\n, i); }int atexit(void (*func)(void)) 当程序正常终止时调用指定的函数 func。 void func() {printf(The program exited!); } int main() {atexit(func);return 0; }void exit(int status) 使程序正常终止。 for(int i 0; i 100; i) {if(i 10) {exit(0);}printf(i%d\n, i); }char *getenv(const char *name) 搜索 name 所指向的环境字符串并返回相关的值给字符串。 printf(JAVA_HOME : %s\n, getenv(JAVA_HOME));int system(const char *string) 由 string 指定的命令传给要被命令处理器执行的主机环境。 system(dir);void *bsearch(const void *key, const void *base, size_t nitems,size_t size, int (*compar)(const void *, const void *)) 执行二分查找 key指向要查找的元素的指针类型转换为 void* 。base指向进行查找的数组的第一个对象的指针类型转换为 void* 。nitemsbase 所指向的数组中元素的个数。size数组中每个元素的大小以字节为单位。compar用来比较两个元素的函数。 #includestdio.h #includestdlib.h #define SIZE 5int compar(const void * a, const void *b) {return *(int *)a - *(int *)b; }int main() {int ns[SIZE] {5, 8, 3, 6, 4};int key 6;int *result (int *)bsearch(key, ns, SIZE, sizeof(int), compar);if(result ! NULL) {printf(*result%d, result%p\n, *result, result);} else {printf(cant find %d\n, key);}for(int i 0; i SIZE; i) {printf(ns[%d]%d, ns[%d]%p\n, i, ns[i], i, ns[i]);}return 0; }void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*)) 数组排序 base指向要排序的数组的第一个元素的指针。nitems由 base 指向的数组中元素的个数。size数组中每个元素的大小以字节为单位。compar用来比较两个元素的函数。 #includestdio.h #includestdlib.h #define SIZE 5int compar(const void * a, const void *b) {return *(int *)a - *(int *)b; }int main() {int ns[SIZE] {5, 8, 3, 6, 4};qsort(ns, SIZE, sizeof(int), compar);for(int i 0; i SIZE; i) {printf(ns[%d]%d, ns[%d]%p\n, i, ns[i], i, ns[i]);}return 0; }int abs(int x) 返回 x 的绝对值。 int a abs(-4); printf(a%d\n, a);div_t div(int numer, int denom) 分子除以分母。 div_t output div(27, 4); printf((27/4) quotient %d\n, output.quot); printf((27/4) remainder %d\n, output.rem);int rand(void) 和 void srand(unsigned int seed) 返回一个范围在 0 到 RAND_MAX 之间的伪随机数。 // 初始化随机数发生器 以CPU的时间作为随机种子所以是伪随机数 srand((unsigned)time(NULL)); // 输出 0 到 49 之间的 5 个随机数 for (int i 0; i 5; i) {printf(%d\n, rand() % 50); }
http://www.dnsts.com.cn/news/136851.html

相关文章:

  • 南京网站设计课程深圳nft网站开发公司
  • 免费dedecms企业网站模板装修设计怎么学
  • 想接做网站的单子福田人才市场
  • 阿里巴巴网官方网站做个微信小程序需要花多少钱
  • 东阳网站建设哪家好wordpress怎么连接数据库
  • 上海网站制作公司介绍营销渠道策略
  • 郑州营销型网站公司电话网站建设就找桥三科技
  • wordpress 站长统计有什么网站可以做试题
  • 石家庄品牌网站建设做网站卖什么发财
  • 有的网站显示正在建设中wordpress 页面瀑布流
  • 那些网站专门做棋牌推广的民治网站设计圳网站建设公司
  • 公司网站没备案找源码的网站
  • 网站开发费用多少嘉祥网站seo
  • 宝塔自助建站源码石家庄网架公司
  • 宜都网站建设如何自己设计一个网页
  • 什么网站可以做新闻听写网站最近收录
  • 网站建设维护工作职责免费公司网页制作
  • 临安农家乐做网站江苏威达建设有限公司网站
  • 象客企业网站做优化排名上海自动化仪表厂
  • div做网站排版建筑公司网站领导致辞
  • 西安建设工程中心交易网站工程项目建设网站
  • 成都协会网站建设展示型网站多少钱
  • 网站托管服务 优帮云响应式网站设计的规范
  • 大连网站设计培训班wordpress 链接本地化
  • 河北网站建设有限公司云服务器建站
  • 别墅设计 网站模板沈阳网站改版
  • 杭州网站建设zj net宽创国际的展馆设计案例
  • 网站开发进度表常见的网络营销模式
  • 定制设计网站外贸营销型网页设计公司
  • 国外网站打不开怎么解决东盟建设工程有限公司网站