网站域名如何备案,谷歌浏览器手机版下载,我的网站怎么转网页呢,苏州工业园区一站式服务中心一、C语言基础知识 入门C语言#xff0c;看这篇就够了#xff1b;适合刚入门编程的萌新小白 编程语言#xff08;programming language#xff09;#xff0c;是用来定义计算机程序的形式语言#xff1b;它是一种被标准化的交流技巧#xff0c;用来向计算机发出指令。 计… 一、C语言基础知识 入门C语言看这篇就够了适合刚入门编程的萌新小白 编程语言programming language是用来定义计算机程序的形式语言它是一种被标准化的交流技巧用来向计算机发出指令。 计算机语言让程序员能够准确地定义计算机所需要使用的数据并精确地定义在不同情况下所应当采取的行动编程语言也俗称 “计算机语言”种类非常的多总的来说可以分成机器语言、汇编语言、高级语言三大类。 C语言属于高级语言最初是用于系统开发工作Linux操作系统内核就是C语言编写C语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样所以采用 C语言作为系统开发语言。 C语言有如下特点 可产生高效率的程序 具有结构化的控制语句 语法限制不太严格程序设计自由度大 可在多种计算机平台上编译移植性好 最广泛被使用的系统程序设计语言之一 语言简洁、紧凑使用方便灵活易于学习 运算符、数据类型丰富具有现代语言的各种数据结构 允许直接访问物理地址能进行位bit操作可以直接对硬件进行操作 C语言应用场景 应用软件编程 数据库编程和数字计算 网络服务器端底层开发、编程 操作系统开发Linux内核、Windows内核等 嵌入式行业Linux驱动、MCU编程等 游戏开发设计游戏引擎cocos2d等 C语言是一门面向过程、抽象化的通用程序设计语言广泛应用于底层开发。C语言能以简易的方式编译、处理低级存储器C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言它为操作系统而生能更直接地与计算机底层打交道精巧、灵活、高效所以C语言是进入编程世界的必修课。 二、快速解读一个C程序 /* 这是一个C语言程序 */
#include stdio.h //包含头文件
int main(int argc, char **argv) //主函数开始
{printf(我只是一条C语言语句...\n) ; //调用函数打印 表示一条语句return 0 ; //函数返回 也表示一条语句
} //主函数语法结束 键盘侠开始了一遍熟悉的操作后最简单的一段C程序就被写了出来此程序看似精简实则也精简手动doge来我们从上往下从前往后解读 #include stdio.h这是一条预处理命令它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作 int main(int argc, char **argv)C语言唯一的主函数主函数就是C语言程序执行的唯一入口main 前面的int是主函数的返回类型argc和argv是函数的参数稍复杂点先有个初步认识**是指针可高级了呢后面会详细介绍标准的写法是加上它俩也可以写成int main(){...}编译器也能识别的 {}此处的花括号表示将这个部分代码括起来是开始和结束的标志代表一个逻辑代码块这里是代表函数C语言的语法规定必须加上 printf()指格式化输出函数主要功能是向标准输出设备按规定格式输出信息它是C语言标准库函数定义于头文件stdio.h所以我们在第一行包含了stdio.h这个文件 return函数的返回值根据函数类型的不同返回的值也是不同的这里是int返回整型变量 三、C语言编程规范 C代码中所有符号均为英文半角符号使用中文符号编译会报错 括号要成对写比如{} [] ()记住有前就有后如果需要删除的话也要成对删除 每条可执行语句结束的末尾不一定是一行可能多行需要有分号在C语言里必须要加否则编译会不通过 函数体内的语句要有明显缩进这是为了方便阅读代码如果你全顶行写我保证看你代码的朋友会用比较激动的语言问候你的通常以按一下Tab键为一个缩进表现出层次 一个说明或一个语句占一行例如包含头文件、一个可执行语句结束有时候也会很多行具体现象具体分析、定义一个宏都需要换行啥你不知道换行很简单我教你首先看下能不能找到键盘上的enter按键再用你尊贵的手手轻轻的敲下enter 编写C语言源代码时应该多使用注释这样有助于对代码的理解其实主要是给自己或者同事看的最好在关键逻辑部分写好注释养成好的编程习惯在C语言中有两种注释方式一种是以/*开始、以*/结束的块注释block comment另一种是以//开始、以换行符结束的单行注释line comment 什么你还不知道哪种写法是规范的看这里 四、标识符、变量、赋值 标识符 标识符只能是字母(AZaz)、数字(09)、下划线(_)组成的字符串并且其第一个字符必须是字母或下划线 标识符中只能包含大小写英文字母、数字、下划线不允许出现如“ 、、#、、%、^、、*、、、/、等标点C语言中的标识符不能使用任何中文字符包括汉字、中文标点假如我定义一个变量int 长度10; 看上去是不是怪怪的显得我们一点都不专业这样整int length 0 ; 一下子就高端大气上档次了人家一看不错小伙子指定练过使用了中文标识符的程序会编不过 标识符不能与c语言的保留字关键字或者库函数名相同比如int char 0 ; int printf 0 ; 注意噢这些操作是不被允许的编译不过 定义变量和函数名以及宏的时候尽量做到见名知义比如定义一系列函数 int can_sing(void){...} int can_dance(void){...} int can_rap(void){...}是不都不消写注释一看就知道这些函数是干嘛的会唱跳RAP对吧还好学习了两年半都会捂嘴笑假如我写成这样int huitiao(void){...} int huichang(void){...} int huirap(void){...}拼音式的编译时也不会报错但好像有点不专业所以大家编程的时候千万不要用拼音中文拼音式的命名一看就比较业余 C语言对大小写敏感因此相同的字母的不同大小写是不同的标识符比如length和Length是不同的标识符我瞅着这俩长得也不大一样 变量和赋值 C语言通过变量为程序设计者提供了使用存储器的手段每个变量代表不同的存储单元有点难懂大白话来了变量就是可以变化的量而每个变量都会有一个名字标识符变量是存放在内存中的所以在程序中每定义一个变量就占用一点内存当然一个普通变量占的内存很小很小尽管使劲造。 C语言的变量由变量名和变量值组成C程序在使用变量之前必须先定义变量定义变量又涉及到数据类型下面会讲到看个简单的例子int a 10 ; unsigned char retry 0 ;a和retry都属于变量而a是整型retry是无符号字符型下图显而易见它们住在不同房子里面房子代表的是存储单元。 变量定义的一般形式为数据类型 变量名; 变量的赋值分为两种方式先定义再赋值定义的时候赋值 C语言不允许连续赋值例如int abc10 ;这样的操作是不合法的你这是违法行为请跟我走一趟 正确的赋值应该是这样的直接上代码 unsigned int inum 0 ; //定义一个无符号整型inum并赋值为0
int itype 1 ; //定义一个整型变量itype并赋值为1
char a, b, c; //定义3个字符型变量a b c不赋值
float f_price 16.8 ; //定义一个浮点型变量f_price并赋值为16.8a 10 ; //给变量a赋值10
b 11 ; //给变量b赋值11
c 1 ; //给变量c赋值1 五、基本数据类型 C语言中数据类型可分为 基本数据类型 构造数据类型 指针类型 空类型 不同的数据类型也有不同的存储范围这些范围可能因编译器而异以下是32位GCC编译器的范围列表以及内存要求和格式说明符 整型是用来表示整数的数据类型C语言中有四种整型基本型短整型长整型无符号型基本型的类型说明符为int占4个字节短整型的类型说明符为short int或short占2个字节长整型的类型说明符为long int或long32位编译环境下占4个字节无符号型的类型说明符为unsigned表示没有符号位0的整数。 实型是用来表示小数的数据类型有两种实型单精度型和双精度型单精度型的类型说明符为float占4个字节有效数字是7位双精度型的类型说明符为double占8个字节有效数字是15位实型常量可以用小数型或指数型表示。 字符型是用来存储单个字符的数据类型它占用一个字节的内存空间可以表示0~255之间的整数或ASCII码C语言中有三种字符型分别是char、signed char和unsigned char它们的区别在于是否有符号C语言中的字符型数据可以用单引号括起来例a、q、$等也可以用ASCII码的数值表示C语言中没有字符串类型但是有字符串常量的概念它是由双引号括起来的一串字符序列以NUL字节结尾例如hello、3210、a等。 枚举类型是一种特殊的数据类型它可以用来定义一组具有离散值的常量枚举类型可以让程序更简洁更易读也可以避免使用魔法数字要定义一个枚举类型需要使用enum关键字后面跟着枚举类型的名称以及用大括号{}括起来的枚举值列表。 数组类型是一种非基础数据类型它可以用来存储一个固定大小的相同类型元素的顺序集合数组的元素可以通过下标访问下标从0开始到元素个数减1结束根据元素的类型数组可以分为字符数组整型数组浮点型数组指针数组结构体数组等字符数组可以用来存储字符串字符串需要一个结束标志\0。 结构体类型是一种复合数据类型它可以用来将多个相关的变量包装成为一个整体使用结构体类型的变量可以表示一条记录例如图书的属性学生的信息等结构体类型的变量可以包含不同类型的数据成员例如基本数据类型指针类型甚至是其他结构体类型结构体类型的定义需要使用struct关键字结构体类型的变量可以参与各种运算也可以用printf函数输出也可以作为函数的参数或返回值。 如果想得到某个类型或某个变量在特定平台上的准确大小可以使用 sizeof 运算符表达式 sizeof(type) 得到对象或类型的存储字节大小。 啥你不知道怎样写来看下面有涉及到指针和一些不太能理解的操作没关系后面会讲这里先混个眼熟 /* 整型 实型 字符型 */
int a 10; // 定义一个整型变量名为a赋值为10
float b 3.14; // 定义一个单精度实型变量名为b赋值为3.14
double c 2.71828; // 定义一个双精度实型变量名为c赋值为2.71828
char d A; // 定义一个字符型变量名为d赋值为字符A
char e 65; // 定义一个字符型变量名为e赋值为整数65对应字符Aunsigned int a 10; // 定义一个无符号整型变量名为a赋值为10
unsigned char b B; // 定义一个无符号字符型变量名为b赋值为字符B
unsigned long c 1000000; // 定义一个无符号长整型变量名为c赋值为1000000 /* 枚举类型 */
/* 分别表示一周的七天每个枚举值都对应一个整数从0开始递增 */
enum weekday {MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY};
/* 把MONDAY的值设为1后面的枚举值依次加1即TUESDAY为2WEDNESDAY为3依此类推 */
enum weekday {MONDAY 1, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY};
/* 声明一个枚举类型的变量需要在变量名前加上枚举类型的名称 */
enum weekday today;
today MONDAY; //可以赋值为枚举值
/* 可以参与整数运算也可以用printf函数输出 */
printf(%d\n, today 1); // 输出2/* 数组类型 */
int a[10]; // 定义一个元素类型为int元素个数为10的数组
a[0] 1; // 给数组的第一个元素赋值为1
a[9] 10; // 给数组的最后一个元素赋值为10
printf(%d\n, a[0] a[9]); // 输出11
char s[6] hello; // 定义一个字符数组存储字符串hello最后一个元素为\0 /* 结构体类型 */
struct book // 定义一个结构体类型名为book
{char title[50]; // 定义一个字符数组类型的数据成员名为titlechar author[50]; // 定义一个字符数组类型的数据成员名为authorint id; // 定义一个整型类型的数据成员名为id
};/* 结构体类型的变量可以在定义时初始化也可以在定义后赋值也可以通过指针访问 */
struct book b1 {C语言程序设计, 谭浩强, 1}; // 定义并初始化一个结构体类型的变量名为b1
struct book b2; // 定义一个结构体类型的变量名为b2
b2.title C程序设计; // 给b2的title数据成员赋值
b2.author 谭浩强; // 给b2的author数据成员赋值
b2.id 2; // 给b2的id数据成员赋值
struct book *p b1; // 定义一个结构体指针类型的变量名为p指向b1的地址
printf(%s\n, p-title); // 通过指针p访问b1的title数据成员输出C语言程序设计/* 结构体类型的变量可以参与各种运算也可以用printf函数输出也可以作为函数的参数或返回值 */
printf(%s\n, b1.title); // 输出b1的title数据成员输出C语言程序设计
printf(%d\n, b1.id b2.id); // 输出b1和b2的id数据成员的和输出3
struct book getBook(); // 声明一个函数返回值类型为结构体类型book
void printBook(struct book b); // 声明一个函数参数类型为结构体类型book 六、格式化输入/输出函数 C语言格式化输入输出语句是指使用一些特定的函数和格式控制符来实现数据的输入和输出常用的函数有printf()和scanf()它们分别用于向标准输出屏幕发送格式化输出和从标准输入键盘读取并格式化输入。 格式控制符是指一些用于指定数据类型和输出格式的字符例如%d表示整数%f表示浮点数%c表示字符%s表示字符串等格式控制符可以和一些修饰符组合例如%5d表示输出宽度为5的整数%0.2f表示输出保留两位小数的浮点数%-10s表示输出左对齐的字符串。 printf函数是一个标准输出函数可以将内容按用户指定的格式输出到屏幕上使用前要包含stdio.h头文件它的函数原型是int printf(const char *format, …);其中format是一个格式控制字符串用于指定输出的格式和内容可以包含普通字符和格式控制符普通字符原样输出格式控制符用于输出对应的变量或常量…是可变参数列表用于提供输出的数据必须和格式控制符一一对应。 scanf函数是一个标准输入函数可以从键盘读取并格式化输入的数据使用前要包含stdio.h头文件它的函数原型是int scanf(const char *format, …);其中format是一个格式控制字符串用于指定输入的格式和内容可以包含普通字符和格式控制符普通字符必须和输入的字符完全匹配格式控制符用于读取对应的数据…是可变参数列表用于提供输入的数据的地址必须和格式控制符一一对应。 看代码敲一个数据转换程序可以输入一个十进制数然后输出它的二进制八进制和十六进制表示 #include stdio.h
int main(int argc, char **argv)
{int num;printf(请输入一个十进制数\n);scanf(%d, num);printf(二进制表示为%b\n, num);printf(八进制表示为%o\n, num);printf(十六进制表示为%x\n, num);return 0;
} 七、C语言常量概念 C语言常量是固定值在程序执行期间不会改变常量可以是任何的基本数据类型比如整数常量、浮点常量、字符常量或字符串字面值还有枚举常量。 常量可以用以下方式定义 直接常量是直接在程序中使用的数值或字符如 3.14, a, hello, 6, 27, -299等 符号常量用一个标识符来代表一个常量值如 #define PI 3.14注意这里#define是预处理指令后面不需要加分号这样在程序中就可以用 PI 来表示 3.14 了 const 修饰符用 const 关键字来修饰一个变量使其成为一个只读变量如 const int a 10a就不能被修改了这个有点不好理解后面会详细介绍如果修改了编译器会直接报错 常量的编程应用有以下几个方面 常量可以提高程序的可读性比如用 PI 来表示圆周率比用 3.14 更直观 常量可以提高程序的可维护性比如如果要修改某个常量值只需要修改一处定义而不需要修改多处使用 常量可以提高程序的效率比如编译器可以对常量进行优化减少运行时的开销 #include stdio.h
#define PI 3.14 //定义符号常量
int main(int argc, char **argv)
{const int a 10; //定义只读变量printf(a %d\n, a);//a 20; //错误不能修改a的值printf(PI %f\n, PI);printf(6 27 %d\n, 6 27); //使用直接常量return 0 ;
} 八、自动类型转换和强制类型转换 自动类型转换是编译器根据代码的上下文环境自行判断的结果不需要在代码中体现出来 一般发生在赋值、运算、函数调用等情况编译器会将较低类型的数据转换为较高类型的数据以保证精度不丢失。 强制类型转换是程序员没错说的就是咱们明确提出的、需要通过特定格式的代码来指明的一种类型转换一般用于满足特定的需求比如截断数据、改变运算结果、转换指针类型等强制类型转换的格式是(type_name) expression其中 type_name 是要转换的目标类型expression 是要转换的表达式。 当两个不同类型的数据进行运算时编译器会将较低类型的数据转换为较高类型的数据然后再进行运算例如 int a 10;
double b 3.14;
double c a b; //自动类型转换将a转换为double类型后再与b相加 当赋值时编译器会将右边的表达式转换为左边变量的类型然后再赋值 int a 10;
float b 3.14;
a b; //自动类型转换将b转换为int类型后再赋值给a会丢失小数部分 当函数调用时操作确实有些复杂了但没关系先认识认识编译器会将实参转换为形参的类型然后再传递 void func(int x)
{printf(x %d\n, x);
}int main(int argc, char **argv)
{double a 3.14;func(a); //自动类型转换将a转换为int类型后再传递给func函数return 0;
} 想要显式地改变变量或表达式的类型时如下 int a 10;
double b 3.14;
a (int) b; //强制类型转换将b转换为int类型后再赋值给a会丢失小数部分 想要提高运算的精度时如下 int a 10;
int b 3;
double c (double) a / b; //强制类型转换将a转换为double类型后再与b相除得到更精确的结果 想要进行不同指针类型之间的转换时如下 int a 10;
int *p a;
char *q (char *) p; //强制类型转换将p转换为char类型的指针后再赋值给q 自动类型转换和强制类型转换综合例子 #include stdio.h
int main(int argc, char **argv)
{int a 10;double b 3.14;double c a b; //自动类型转换将a转换为double类型后再与b相加printf(c %f\n, c);int d (int) b; //强制类型转换将b转换为int类型后赋值给d会丢失小数部分printf(d %d\n, d);return 0;
} 九、C语言运算符 C语言中的运算符包括以下几种类型 算术运算符用于进行数值计算如加减乘除、取余、自增自减等 关系运算符用于进行比较运算如大于小于、等于不等于、大于等于小于等于等 逻辑运算符用于进行布尔运算如与或非、短路与短路或等 位运算符用于进行二进制位操作如按位与按位或按位异或、左移右移、取反等。 赋值运算符用于给变量赋值如等号、加等减等、左移等右移等等 杂项运算符用于完成一些特殊的功能如条件运算符、逗号运算符、取地址运算符、间接访问运算符、sizeof运算符等 运算符的结合性和优先级 C语言一共有15个优先级从高到低依次是括号、单目运算符、乘除余、加减、移位、关系运算符、相等运算符、按位与、按位异或、按位或、逻辑与、逻辑或、条件运算符、赋值运算符和逗号。 C语言的结合性是指当一个表达式中有相同优先级的运算符时计算的顺序是从左到右还是从右到左除了单目运算符如–!等、条件运算符?:和赋值运算符-等是从右向左结合的之外其余的都是从左向右结合的。 没整明白?上图 算术运算符 (加)用于计算两个数值的和如3 5 8 - (减)用于计算两个数值的差如7 - 2 5 * (乘)用于计算两个数值的积如4 * 6 24 / (除)用于计算两个数值的商如10 / 2 5 % (取余模运算)用于计算两个数值的余数如20 % 8 4注意两个数值必须是整数 (自增)用于将一个数值加1如a 5a后a 6可以分为前缀自增和后缀自增前缀自增先加后用后缀自增先用后加 -- (自减)用于将一个数值减1如b 10b–后b 9可以分为前缀自减和后缀自减前缀自减先减后用后缀自减先用后减 算术运算符可以用于构成表达式表达式是由运算符和操作数组成的可以计算出一个值如a b * c是一个表达式它的值取决于abc的值和运算符的优先级和结合性。 使用算术运算符的编程实例 #include stdio.h
int main(int argc, char **argv)
{int a 3, b 5;int c a b; // c 8int d a - b; // d -2int e a * b; // e 15int f a / b; // f 0int g a % b; // g 3double h (double)a / b; // h 0.6printf(c %d, d %d, e %d, f %d, g %d, h %f\n, c, d, e, f, g, h);return 0;
} 关系运算符 (大于)用于判断两个数值的大小关系如果左边的数值大于右边的数值返回1否则返回0如5 3返回12 4返回0 (大于等于)用于判断两个数值的大小关系如果左边的数值大于或等于右边的数值返回1否则返回0如6 4返回13 5返回04 4返回1 (小于)用于判断两个数值的大小关系如果左边的数值小于右边的数值返回1否则返回0如2 5返回14 3返回0 (小于等于)用于判断两个数值的大小关系如果左边的数值小于或等于右边的数值返回1否则返回0如3 6返回15 3返回04 4返回1 (等于)用于判断两个数值是否相等如果相等返回1否则返回0如4 4返回13 5返回0注意等于运算符是双等号不要和赋值运算符单等号混淆 ! (不等于 )用于判断两个数值是否不相等如果不相等返回1否则返回0如3 ! 5返回14 ! 4返回0 关系运算符可以用于构成关系表达式关系表达式是由关系运算符和操作数组成的可以判断出一个真假值如a b是一个关系表达式它的值取决于ab的值和关系运算符的结果如果a b成立值为1否则值为0。关系表达式常用于条件判断和循环控制中如if (a b) { … }while (a ! b) { … }。 使用关系运算符的编程实例代码 #include stdio.h
int main(int argc, char **argv)
{int a 3, b 5;int c a b; // c 0int d a b; // d 1int e a b; // e 0int f a b; // f 1int g a b; // g 0int h a ! b; // h 1printf(c %d, d %d, e %d, f %d, g %d, h %d\n, c, d, e, f, g, h);return 0;
} 逻辑运算符 || (或)用于判断两个逻辑表达式的真假关系如果左边或右边的表达式为真返回1否则返回0如1 || 0返回10 || 0返回0 (且)用于判断两个逻辑表达式的真假关系如果左边和右边的表达式都为真返回1否则返回0如1 1返回11 0返回0 ! (非)用于取反一个逻辑表达式的真假值如果表达式为真返回0如果表达式为假返回1如!1返回0!0返回1 逻辑运算符可以用于构成逻辑表达式逻辑表达式是由逻辑运算符和操作数组成的可以判断出一个真假值如a || b是一个逻辑表达式它的值取决于ab的值和逻辑运算符的结果如果a || b成立值为1否则值为0逻辑表达式常用于条件判断和循环控制中如if (a || b) { … }while (!a) { … }。 使用逻辑运算符的编程实例代码 #include stdio.h
int main(int argc, char **argv)
{int a 3, b 5;int c a b b a; // c 0int d a b || b a; // d 1int e !(a b); // e 1printf(c %d, d %d, e %d\n, c, d, e);return 0;
} 位运算符 (按位与)用于对两个数值类型的二进制位进行逐位与运算如果两个位都为1返回1否则返回0如3 5返回1因为3的二进制是00115的二进制是01010011 0101 0001 | (按位或)用于对两个数值类型的二进制位进行逐位或运算如果两个位有一个为1返回1否则返回0如3 | 5返回7因为3的二进制是00115的二进制是01010011 | 0101 0111 ^ (按位异或)用于对两个数值类型的二进制位进行逐位异或运算如果两个位不同返回1否则返回0如3 ^ 5返回6因为3的二进制是00115的二进制是01010011 ^ 0101 0110 ~ (按位取反)用于对一个数值类型的二进制位进行逐位取反运算如果位为1返回0如果位为0返回1如~3返回-4因为3的二进制是0011~3的二进制是1100按补码规则1100的原码是1011即-4。 (左移)用于将一个数值类型的二进制位按指定的位数向左移动移出的位被丢弃右边的空位补0如3 2返回12因为3的二进制是00113 2的二进制是1100即12 (右移)用于将一个数值类型的二进制位按指定的位数向右移动移出的位被丢弃左边的空位根据符号位补0或1如-3 2返回-1因为-3的二进制是1011-3 2的二进制是1111即-1 位运算符可以用于对数值类型的二进制位进行操作可以实现一些高效的算法如位图加密压缩。 使用位运算符的编程实例代码 #include stdio.h
int main(int argc, char **argv)
{unsigned char a 18, b 12;printf(a 2 %d\n, a 2); //输出72printf(a 2 %d\n, a 2); //输出4printf(~a %d\n, ~a); //输出237printf(a b %d\n, a b); //输出0printf(a | b %d\n, a | b); //输出30printf(a ^ b %d\n, a ^ b); //输出30return 0;
} 赋值和杂项运算符 赋值运算符是用来给变量赋值的运算符有简单赋值和复合赋值-*/%^|两种简单赋值就是把右边的表达式的值赋给左边的变量复合赋值就是把左边的变量与右边的表达式按照指定的运算符进行计算并把结果赋给左边的变量。 杂项运算符是指一些不属于其他类别的运算符有三元条件运算符?:、逗号运算符,、取地址运算符和间接访问运算符*三元条件运算符是用来根据一个条件表达式来选择两个表达式中的一个执行逗号运算符是用来连接两个或多个表达式并返回最后一个表达式的值取地址运算符是用来获取一个变量或对象在内存中的地址间接访问运算符是用来通过一个指针变量来访问它所指向的变量或对象。 sizeof是一个关键字不是一个函数它可以用来获取一个类型或对象在内存中占用的字节数它可以接收一个类型名或一个表达式作为参数并返回一个无符号整数sizeof对于确定数组长度、分配动态内存、检查数据类型等操作很有用。 直接看代码这里有些赋值和杂项运算符的栗子 #include stdio.hint main(int argc, char **argv)
{// 赋值运算符的例子int a 10; // 简单赋值printf(a %d\n, a);a 5; // 复合赋值相当于 a a 5;printf(a %d\n, a);// 杂项运算符的例子int b (a 10) ? 1 : 0; // 三元条件运算符相当于 if (a 10) b 1; else b 0;printf(b %d\n, b);int c (a, b); // 逗号运算符相当于 a; c b;printf(c %d\n, c);int *p a; // 取地址运算符把变量a的地址赋给指针pprintf(p %p\n, p);*p 20; // 间接访问运算符把指针p所指向的变量即a赋值为20printf(*p %d\n, *p);// sizeof的例子int d[10]; // 定义一个长度为10的整型数组printf(sizeof(int) %lu\n, sizeof(int)); // 输出一个int类型占用的字节数printf(sizeof(d) %lu\n, sizeof(d)); // 输出数组d占用的字节数printf(sizeof(d[0]) %lu\n, sizeof(d[0])); // 输出数组d中第一个元素占用的字节数} 十、C语言流程控制 C语言的分支结构是指程序根据条件有选择地执行不同的语句或代码块的结构C语言的分支结构主要有两种if语句和switch语句。 分支结构 if语句是最常见的分支结构它可以根据一个或多个条件表达式来选择执行不同代码段if语句有以下几种形式 // 单分支
if (条件表达式) {语句块1; // 如果条件表达式为真执行这个语句块
}// 双分支
if (条件表达式) {语句块1; // 如果条件表达式为真执行这个语句块
} else {语句块2; // 如果条件表达式为假执行这个语句块
}// 多分支
if (条件表达式1) {语句块1; // 如果条件表达式1为真执行这个语句块并跳过后面的else if和else
} else if (条件表达式2) {语句块2; // 如果条件表达式1为假且条件表达式2为真执行这个语句块并跳过后面的else if和else
} else if (条件表达式3) {语句块3; // 如果前面的所有条件表达式都为假且条件表达式3为真执行这个语句块并跳过后面的else if和else
} ...
else {语句块n; // 如果前面的所有条件表达式都为假执行这个默认的语句块
} 有一种稍微复杂一点并有意思操作嵌套if-else语句嵌套if-else意思是在if-else语句内再写if-else语句如下实例 #include stdio.hint main(int argc, char **argv)
{// 输入一个成绩判断其等级int score;printf(请输入一个成绩);scanf(%d, score);if (score 0 score 100) { // 判断成绩是否在合法范围内if (score 90) { // 判断成绩是否为优秀printf(优秀\n);} else if (score 80) { // 判断成绩是否为良好printf(良好\n);} else if (score 60) { // 判断成绩是否为及格printf(及格\n);} else { // 其他情况为不及格printf(不及格\n);}} else { // 成绩不在合法范围内输出错误信息printf(输入错误\n);}
} switch语句是一种多路分支结构它可以根据一个整型或字符型变量或常量的值来选择执行不同的代码段switch语句有以下形式 switch (变量或常量) {case 值1:代码段1; // 如果变量或常量等于值1执行这个代码段并跳出switch结构除非使用breakbreak;case 值2:代码段2; // 如果变量或常量等于值2执行这个代码段并跳出switch结构除非使用breakbreak;case 值3:代码段3; // 如果变量或常量等于值3执行这个代码段并跳出switch结构除非使用breakbreak;...default:默认代码段; // 如果变量或常量与所有case中的值都不匹配执行这个默认的代码段并跳出switch结构除非使用breakbreak;
} switch语句中可以使用break和continue关键字来控制程序的流程break关键字用于跳出switch语句执行switch语句后面的代码如果不加break程序会一直往后执行直到遇到break或者switch语句结束。 continue关键字用于跳过本次循环开始下一次循环在switch语句中continue必须结合循环使用否则会报错。 default是一个关键字它只用在switch语句中用于指定当switch语句中的所有case都不匹配时所要执行的代码块default语句可以放在switch语句的任何位置但通常放在最后以便清晰地表示默认情况在default语句后面也需要加break以便跳出switch语句否则程序会继续执行后面的case。 有意思的code栗子又来了呢下面实例充分体现了continue、default、break在switch中的用法 #include stdio.hint main(int argc, char **argv)
{// 输入一个数字判断其奇偶性int num;printf(请输入一个数字);scanf(%d, num);switch (num % 2) {case 0:printf(偶数\n);break; // 跳出switch语句case 1:printf(奇数\n);break; // 跳出switch语句default:printf(输入错误\n);break; // 跳出switch语句}// 输入一个字符判断其是否为元音字母char ch;printf(请输入一个字符);scanf(%c, ch);for (int i 0; i 5; i) { // 循环5次switch (ch) {case a:case e:case i:case o:case u:printf(元音字母\n);continue; // 跳过本次循环开始下一次循环default:printf(非元音字母\n);continue; // 跳过本次循环开始下一次循环}}
} 循环结构 C语言循环结构是指可以重复执行一段代码的语句C语言提供了三种循环结构分别是 for 循环、while循环和do while循环它们的区别在于循环条件的判断位置和执行次数。 for 循环是一个由四个部分组成的语句分别是初始化、条件、增量和循环体它可以用来执行固定次数的循环或者根据某个变量的变化来控制循环。 while 循环是一个由两个部分组成的语句分别是条件和循环体它可以用来执行不确定次数的循环或者根据某个表达式的真假来控制循环它是一个入口条件循环也就是说在每次执行循环体之前都要判断条件是否成立。 do while 循环也是一个由两个部分组成的语句分别是循环体和条件它与 while 循环的区别在于它是一个出口条件循环也就是说在每次执行完循环体之后才判断条件是否成立因此do while 循环至少会执行一次。 看着描述好像懂了耶眼睛会了手不会来上操作 for 循环语法示意 for (初始化; 条件; 增量) {循环体;
} for 循环代码例子 // 计算 1 到 100 的和
#include stdio.h
int main()
{int i 0;int sum 0;for (i 1; i 100; i) {sum sum i;}printf(sum %d\n, sum);return 0 ;
} while 循环语法示意 while (条件) {循环体;
} while 循环代码例子 // 输出从 n 到 m 的所有偶数
#include stdio.h
int main()
{int n 10;int m 20;while (n m) {if (n % 2 0) {printf(%d\n, n);}n;}return 0 ;
} do while 循环语法示意 do {循环体;
} while(条件); do while 循环代码例子 // 输入一个正整数输出它的阶乘
#include stdio.h
int main()
{int n, i;unsigned long long fact 1;printf(输入一个整数: );scanf(%d,n);// 如果输入为负数显示错误信息if (n 0)printf(错误负数没有阶乘。);else {for(i1; in; i) {fact * i; // fact fact*i;}printf(%d 的阶乘为 %llu, n, fact);}return 0;
} 使用for 循环的注意事项 通常适用于循环次数已知的场景 for 循环的初始化部分可以是多个赋值语句用逗号隔开 for 循环的条件部分可以是多个表达式用逻辑运算符连接 for 循环的增量部分可以是多个语句用逗号隔开 for 循环中可以使用 break 和 continue 控制循环流程 使用while 循环的注意事项 通常适用于循环次数未知的场景 while 循环必须先判断条件是否成立然后决定是否执行循环体语句 while 循环中必须有改变条件表达式的语句否则会造成死循环 while 循环中可以使用 break 和 continue 控制循环流程 使用do while 循环的注意事项 do while 循环至少执行一次循环体 do while 循环先执行循环体语句然后判断条件是否成立再决定是否继续循环 do while 循环中必须有改变条件表达式的语句否则会造成死循环 do while 循 环中可以使用 break 和 continue 控制循环流程 什么你还不知道在循环结构中使用continue和break这必须安排上一下几段代码示意了for和while循环分别使用break和continue // 输出 1 到 10当 i 等于 5 时跳出循环
#include stdio.h
int main()
{int i 0;for (i 1; i 10; i) {printf(%d\n, i);if (i 5) {break;}}return 0 ;
} // 输出从 n 到 m 的所有奇数跳过偶数
#include stdio.h
int main()
{int n 10;int m 20;int i n;for (i n; i m; i) {if (i % 2 0) {continue;}printf(%d\n, i);}return 0 ;
} // 输入一个正整数输出它的因子如果输入负数或零则结束循环
#include stdio.h
int main()
{int n, i;while (1) {printf(输入一个正整数: );scanf(%d,n);// 如果输入为负数或零跳出循环if (n 0) {break;}printf(%d 的因子有: , n);for(i1; in; i) {if (n % i 0) { printf(%d ,i);}}printf(\n);}return 0;
} // 输入一个正整数判断它是否为素数如果输入负数或零则跳过判断继续输入
#include stdio.h
int main()
{int n, i, flag;while (1) {printf(输入一个正整数: );scanf(%d,n);// 如果输入为负数或零跳过判断继续输入if (n 0) {continue;}flag 1; // 假设是素数// 判断是否有除了1和自身以外的因子for(i2; in/2; i){// 若无余则不是素数if(n%i0){flag0;break;}}if(flag1)printf(%d 是素数。\n,n);elseprintf(%d 不是素数。\n,n);}return 0 ;
} 啥你还写不出来来我把手借给你多写多练多想绝对的能行还想玩点高级的安排循环嵌套 /* while循环嵌套打印9*9乘法口诀表 */
#include stdio.h
int main()
{int x 1; //定义第一个乘数int y 1; //定义第二个乘数while (x 9) //外层循环控制行{y 1; //每次进入内层循环时将y重置为1while (y x) //内层循环控制列{printf(%d*%d%d\t, y, x, x * y); //打印乘法式子和结果\t表示制表符用来对齐输出y; //y自增1继续下一列的打印}printf(\n); //内层循环结束后换行输出下一行x; //x自增1继续下一行的打印}return 0;
} /* for循环嵌套打印9*9乘法口诀表 */
#include stdio.h
int main()
{int i, j; //定义两个循环变量for (i 1; i 9; i) //外层循环控制行{for (j 1; j i; j) //内层循环控制列{printf(%d*%d%d\t, j, i, i * j); //打印乘法式子和结果\t表示制表符用来对齐输出}printf(\n); //内层循环结束后换行输出下一行}return 0;
} 再花里胡哨一点的操作 /* for嵌套while循环打印9*9乘法口诀表 */
#include stdio.h
int main()
{int x 1; //定义第一个乘数int y; //定义第二个乘数for (x 1; x 9; x) //外层循环控制行{y 1; //每次进入内层循环时将y重置为1while (y x) //内层循环控制列{printf(%d*%d%d\t, y, x, x * y); //打印乘法式子和结果\t表示制表符用来对齐输出y; //y自增1继续下一列的打印}printf(\n); //内层循环结束后换行输出下一行}return 0;
} 十一、合理使用goto语句 简单来说goto语句是一种无条件跳转语句它可以让程序跳转到同一函数内的某个标记处它的语法是 goto label; //跳转到label处
//其他代码
label: //标记位置 但是goto语句也有很多缺点比如 它会导致程序逻辑混乱难以理解和修改 它会破坏程序结构使得代码不符合自顶向下的设计原则 它会增加程序出错的风险比如造成死循环或内存泄漏 因此在任何编程语言中都不建议使用goto语句任何使用goto语句的程序都可以改写成不需要使用goto语句的写法但在Linux驱动开发中会常见到它。 Linux内核或驱动中大量使用goto语句主要为了处理错误情况和释放内存空间例如下面是一个简单的驱动程序片段它使用goto语句来跳转到不同的标签处根据不同的错误码来释放已经分配的资源这里直接是起飞的操作涉及到Linux驱动开发代码先看看不懂没关系我来手把手 static int __init hello_init(void) //定义一个静态的初始化函数
{int ret -1; //定义一个返回值变量初始值为-1printk(KERN_INFO Hello World enter\n); //打印一条内核信息hello_class class_create(THIS_MODULE,hello_class); //创建一个设备类if(IS_ERR(hello_class)) //判断设备类是否创建成功{ret PTR_ERR(hello_class); //如果失败获取错误码并赋值给retgoto class_err; //跳转到class_err标签处结束函数}hello_device device_create(hello_class,NULL,MKDEV(HELLO_MAJOR,0),NULL,hello_device); //创建一个设备节点if(IS_ERR(hello_device)) //判断设备节点是否创建成功{ret PTR_ERR(hello_device); //如果失败获取错误码并赋值给retgoto device_err; //跳转到device_err标签处释放设备类资源并结束函数}cdev_init(hello_cdev,hello_ops); //初始化一个字符设备ret cdev_add(hello_cdev,MKDEV(HELLO_MAJOR,0),HELLO_COUNT); //添加字符设备到系统if(ret 0) //判断字符设备是否添加成功goto cdev_err; //如果失败跳转到cdev_err标签处释放设备节点和设备类资源并结束函数return 0; //如果成功返回0cdev_err: device_destroy(hello_class,MKDEV(HELLO_MAJOR,0)); //释放设备节点资源
device_err:class_destroy(hello_class); //释放设备类资源
class_err:return ret;
} 看不懂有点难?没关系先看代码结构别跟细节因为涉及到很多后续的内容跟着我一起搞学习相信屏幕前的你也能写出上面的驱动代码是不是立马感觉高大上了许多。 十二、C语言数组 数组概念 C语言数组是一种数据结构它可以存储固定大小的相同类型的元素的顺序集合数组的元素可以通过下标访问下标从0开始C语言支持一维、二维和多维数组其中二维数组可以看作是由一维数组组成的数组。 一维数组可以类比为一排连续的房间每个房间都有一个门牌号从 0 开始递增。每个房间里可以存放相同类型的物品例如书籍、衣服等。要访问或修改某个房间里的物品就需要知道它的门牌号也就是数组下标。要找到第一个房间的位置就需要知道它的地址也就是数组名。 二维数组可以类比为一个楼层每个楼层有多排多列的房间每个房间都有一个行号和列号从 0 开始递增。每个房间里可以存放相同类型的物品例如家具、电器等要访问或修改某个房间里的物品就需要知道它的行号和列号也就是数组下标。要找到第一个楼层的位置就需要知道它的地址也就是数组名。 一维数组 一维数组是一组相同类型的数据集合每个元素通过数组名和一个下标唯一确定。 在内存中是按照顺序从低地址到高地址存储的每个元素占用相同大小的空间。 首地址是数组名所代表的地址也就是第一个元素的地址可以用指针来访问或修改数组的元素例如 int *p a; 表示定义一个指针 p 指向 a 数组的首地址涉及指针后面会介绍。 定义数组要指定数组的类型、名称和大小定义数组的一般形式是类型说明符 数组名[常量表达式];例如 int a[10]; 表示定义一个名为 a 的整型数组它有 10 个元素 初始化数组要给数组的元素赋初值可以在定义时用花括号列出例如 int a[5] {1, 2, 3, 4, 5}; 表示定义并初始化一个一维数组如果没有给出所有元素的初值剩余的元素会自动为0 使用数组要通过下标访问或修改数组的元素下标从 0 开始例如 a[0] 10; 表示把 a 数组的第一个元素改为 10可以用循环遍历数组的所有元素例如 for (int i 0; i 5; i) printf(%d , a[i]); 表示打印出 a 数组的所有元素 操作数组要对数组进行排序、查找、插入、删除等操作需要编写相应的函数或算法例如冒泡排序、二分查找等也可以用 C 标准库提供的函数来操作字符串字符数组例如 strcpy、strcat、strcmp 等 二维数组 多维数组是元素为数组的数组可以用一个数组名和多个下标来访问或修改每个元素. 定义多维数组的一般形式是类型说明符 数组名[常量表达式1][常量表达式2]...[常量表达式n];其中类型说明符表示数组元素的类型常量表达式表示每一维的长度例如int c[2][3][4]; 表示定义一个三维整型数组 c它有 2 个二维数组每个二维数组有 3 个一维数组每个一维数组有 4 个整数。 初始化多维数组时可以用花括号包含多层值来初始化每一维的元例如int d[2][3] {{1, 2, 3}, {4, 5, 6}}; 表示定义并初始化一个二维整型数组 d它有两个一维数组分别为 {1, 2, 3} 和 {4, 5, 6}。 访问或修改多维数组元素时需要用到数组名和多个下标每个下标从 0 开始到对应长度减 1 结束例如printf(%d\n, d[0][1]); 表示打印出 d 数组的第一个一维数组下标为 0)的第二个元素下标为2。 字符串与数组 字符串是由一系列字符组成的通常用双引号包围例如Hello 是一个字符串它由 5 个字符 H, e, l, l 和 o 组成。 字符数组是一种特殊的数组它的元素都是字符类型例如char a[10]; 表示定义一个长度为 10 的字符数组 a它可以存放 10 个字符。 C语言中没有专门的字符串类型所以通常用字符数组来表示和存储字符串例如char b[6] Hello; 表示定义并初始化一个长度为 6 的字符数组 b它存放了字符串 Hello\0\0是字符串结束标记。 字符串必须以\0结束这个 \0 表示字符串的结束标志也占用一个字节的空间所以定义字符数组时要留出足够的空间来存放 \0。例如char c[6] Hello; 中 c 数组的最后一个元素就是 \0。 字符串可以直接赋值给字符指针但不能直接赋值给字符数组。例如char *d Hello; 是正确的但 char e[6]; e Hello; 是错误的又是指针可见C语言中指针很重要。 字符指针指向的字符串常量是不可修改的但字符数组中的元素是可以修改的。例如d[0] h; 是错误的但 e[0] h; 是正确的。 C语言中有很多常用的字符串函数可以对字符串进行各种操作。介绍一些常见的字符串函数 strlen返回字符串的长度不包括结束符\0 strcpy把一个字符串复制到另一个字符串包括结束符\0 strcmp比较两个字符串的大小如果相等返回0如果第一个字符串大于第二个返回正数否则返回负数。 strcat把一个字符串追加到另一个字符串的末尾覆盖原来的结束符\0 strstr查找一个子串在一个主串中第一次出现的位置并返回指向该位置的指针。如果没有找到则返回NULL strtok把一个字符串按照指定的分隔符分割成若干个子串并依次返回每个子串的指针。如果没有更多子串则返回NULL 这些函数都需要包含头文件string.h才能使用。 数组应用-冒泡排序 数组的常规操作如下 int a[10]; // 声明了一个整型数组包含10个元素
char b[20]; // 声明了一个字符数组包含20个元素
double c[5]; // 声明了一个双精度浮点数数组包含5个元素a[0] 1; // 给数组a的第一个元素赋值为1
b[19] z; // 给数组b的最后一个元素赋值为z
c[2] 3.14; // 给数组c的第三个元素赋值为3.14// 获取数组a的长度并打印结果
int len_a sizeof(a) / sizeof(a[0]);
printf(The length of array a is %d\n, len_a);// 获取数组b的长度并打印结果
int len_b sizeof(b) / sizeof(b[0]);
printf(The length of array b is %d\n, len_b); // 使用for循环遍历数组a并打印每个元素的值
for (int i 0; i 10; i) {printf(%d , a[i]);
}
printf(\n);// 使用while循环遍历数组b并打印每个元素的值
int j 0;
while (j 20) {printf(%c , b[j]);j;
}
printf(\n); 利用数组实现冒泡排序 //冒泡排序函数
void bubble_sort(int num[],int n) //存放要排序数的数组要排序数的个数
{int i,j,t; //i,j为遍历变量t为临时交换变量for(i0;in-1;i) //控制内层循环次数{//控制每次循环最小的比较次数for(j0;jn-1-i;j) //这里-i是因为每次冒完泡出来的那个数位置固定了不用管{//比较相邻的两个数if(num[j]num[j1]) //把大的数冒泡到后面即从小到大排序{//把第一个数与第二个数交换位置tnum[j1];num[j1]num[j];num[j]t;}}}
} 注意事项 定义数组时数组的长度必须是常量、常量表达式或宏定义不能是变量例如int n 10; int a[n]; 这样是错误的应该改为 #define N 10; int a[N]; 或 const int n 10; int a[n];。 初始化数组时如果花括号内提供的初始值个数少于数组长度那么剩余的元素会自动初始化为0例如int b[5] {1, 2}; 表示 b 数组的前两个元素为 1 和 2后三个元素为 0。 访问或修改数组元素时不能越界即下标不能小于 0 或大于等于数组长度例如int c[3] {1, 2, 3}; printf(%d\n, c[-1]); 是错误的因为 -1 越界了。 数组名代表数组首元素的地址不能被赋值或修改例如int d[4] {4, 5, 6, 7}; d d 1; 是错误的因为 d 是一个常量指针。 十三、C语言函数 函数是一组一起执行一个任务的语句函数可以接收用户传递的参数也可以不接收函数可以返回一个值也可以不返回将代码段封装成函数的过程叫做函数定义。 C语言有很多自带的库函数例如数学函数字符串函数输入输出函数等也可以自己定义函数用户自定义函数。 C语言函数的概念是函数是一组一起执行一个任务的语句一个函数里面可以调用 n 个函数即大函数调用小函数小函数又调用“小小”函数。 自定义函数 C语言函数的构成包括返回值类型函数名参数列表和函数体函数的定义规则是函数名必须以字母或下划线开头不能与关键字重名不能与其他函数或变量同名;函数的返回值类型和参数类型必须明确指定函数体必须用大括号包围下面是一个用户自定义函数 int max(int a, int b) // 返回值类型为int函数名为max参数列表为(int a, int b)
{if (a b) // 函数体return a;elsereturn b;
} C语言函数的参数是指在调用函数时传递给函数的数据参数分为形参和实参两种形参形式参数是指在函数定义中出现的参数它没有数据只能等到函数被调用时接收传递进来的数据例如 int max(int a, int b) // a和b就是形参
{// 函数体
} 实参实际参数是指在函数调用中出现的参数它有具体的值可以是常量、变量或表达式例如 int x 10, y 20; // 定义两个变量x和y
int z max(x, y); // x和y就是实参 当调用一个有参数的函数时会将实参的值拷贝给形参指向的内存空间又涉及到指针这样形参就可以在函数体中使用实参的值来完成任务函数的传参概念十分重要传递的是值C语言函数的调用是指在程序中使用已经定义好的函数来完成某个功能。 有参函数是指在主调函数调用被调函数时主调函数通过参数向被调函数传递数据如上代码所示就是有参函数。 无参函数是指在主调函数调用被调函数时主调函数不需要向被调函数传递数据例如 void hello() // 定义一个无参函数没有参数
{printf(Hello world!\n); // 函数体
}
hello(); // 调用无参函数不需要传递参数 有参函数和无参函数不能单单凭有无返回值确定为哪类函数最核心本质就是主调函数需不需要向被调函数传递数据。 函数返回值是指在执行完一个有返回值的函数后该值会返回给主调方使用返回值的类型必须与定义时的类型相同或兼容自动/强制类型转换 int add(int a, int b) // 定义一个有返回值的函数返回值类型为int
{return a b; // 返回a和b的和作为返回值
}int x 10, y 20; // 定义两个变量x和y
int z add(x, y); // 调用有返回值的函数并将返回值赋给z
printf(The sum is %d\n, z); // 输出结果void hello() // 定义一个无返回值的函数没有return语句
{printf(Hello world!\n);
}hello(); // 调用无返回值的函数不需要接收任何返回值 C语言中的return是一个关键字它用于在函数中返回一个值或者终止函数的执行例如 int add(int a, int b) // 定义一个有返回值的函数返回值类型为int
{return a b; // 返回a和b的和作为返回值
}void hello() // 定义一个无返回值的函数没有return语句
{printf(Hello world!\n);
}int max(int a, int b) // 定义一个有返回值的函数返回值类型为int
{if (a b) // 如果a大于breturn a; // 返回a作为最大值else // 否则return b; // 返回b作为最大值
} 在上面的例子中add函数使用了return语句来将a和b的和作为返回值传递给主调方hello函数没有使用return语句当然它也可以使用return;不接参数表示直接返回因为它不需要返回任何值max函数使用了多个return语句来根据条件提前结束函数并返回最大值。 当执行到return语句时会将return后面的表达式如果有的结果赋给主调方指定的变量如果有然后退出当前函数并回到主调方继续执行例如 int x 10, y 20; // 定义两个变量x和yint z add(x, y); // 调用add函数并将x和y作为参数传递给它将返回值赋给zprintf(The sum is %d\n, z); // 输出结果hello(); // 调用hello函数不需要传递参数或接收返回值printf(The max is %d\n, max(x, y)); // 调用max函数并将x和y作为参数传递给它直接输出其返回值 递归函数 C语言递归函数是指一个函数在其函数体内调用自身的函数递归函数可以用来解决一些数学问题如阶乘、斐波那契数列等递归函数的优点是可以简化代码量缺点是运行效率较低。 递归函数使用场景有很多比如树形菜单快速排序汉诺塔问题等。这些场景都是可以把问题分解为相同或相似的子问题然后用同一个函数来解决。 递归实现阶乘 定义一个函数fac接受一个整数n作为参数。 如果n小于等于1那么返回1 否则返回n乘以fac(n-1) //定义阶乘函数
int fac(int n)
{if (n 1) { //基准情况return 1;} else { //一般情况return n*fac(n - 1); //调用自身}
} 再来个复杂一点的例子如快速排序 //快速排序
void quicksort(int a[], int left, int right)
{if (left right) {int i left;int j right;int pivot a[left]; //选取第一个元素作为基准while (i j) {while (i j a[j] pivot) //从右向左找到第一个小于基准的元素j--;if (i j)a[i] a[j]; //将该元素放到左边while (i j a[i] pivot) //从左向右找到第一个大于基准的元素i;if (i j)a[j--] a[i]; //将该元素放到右边}a[i] pivot; //将基准放到中间位置quicksort(a, left, i - 1); //对左半部分递归排序quicksort(a, i 1, right); //对右半部分递归排序}
} 简单算法实例 有5个人坐在一起问第5个人多少岁他说比第4个人大2岁。问第4个人岁数他说比第3个人大2岁。问第3个人又说比第2人大两岁。问第2个人说比第1个人大两岁。最后 问第1个人他说是10岁。请问第5个人多大 #include stdio.h
//定义年龄函数
int age(int n)
{if (n 1) { //基准情况return 10;} else { //一般情况return age(n - 1) 2; //调用自身}
}int main()
{printf(第5个人的年龄是%d岁, age(5)); return 0;
} 静态/外部函数 函数的声明和定义可以用static或extern修饰符来指定函数的存储类别。 static修饰符表示函数是内部的也就是说它只能被本文件中其他函数所调用它的作用域只局限于所在文件在定义内部函数时在函数名和函数类型的前面加static即 static 类型名 函数名(形参表); 使用内部函数可以使函数和外部变量放在文件的开头前面都加static使之局部化表示其他文件不能访问。 extern修饰符表示函数是外部的也就是说它可以被其他文件中的函数所调用它的作用域不受文件限制在定义外部函数时在函数名和函数类型的前面加extern即 extern 类型名 函数名(形参表); 使用外部函数可以使多个文件共享同一个功能模块以下是内部函数和外部函数的区别 file1.c // file1.c#include stdio.h// 内部静态变量
static int a 10;// 外部非静态变量
int b 20;// 内部静态默认函数
static void func1()
{printf(file1: func1: a %d, b %d\n, a, b);
}// 外部非静态默认函数
void func2()
{printf(file1: func2: a %d, b %d\n, a, b);
}int main()
{func1();func2();return 0;
} file2.c // file2.c#include stdio.h// 引用外部非静态变量
extern int b;// 引用外部非静态默认函数
void func2();int main()
{// 调用外部非静态默认变量和函数printf(file2: main: b %d\n, b);func2();// 不能调用内部静态默认变量和函数// printf(file2: main: a %d\n, a); // 错误未声明标识符“a”// func1(); // 错误未声明标识符“func1”return 0;
} 输出结果
file1: func1: a 10, b 20
file1: func2: a 10, b 20file2: main: b 20
file1: func2: a 10, b 20 在不同的文件中内部静态默认变量和函数只能被本文件中其他变量或者函调用而外部非静态默认变量和函则可以被其他文件中其他变量或者函调用。 十四、变量存储类型 C语言变量的存储类别决定了变量的作用域和生存周期根据存储类别变量可以分为以下几种 局部变量在函数内部定义的变量只能在该函数内部使用每次函数调用时创建函数结束时销毁局部变量可以是自动的auto、静态的static或寄存器的register 全局变量在函数外部定义的变量可以在整个程序文件中使用程序开始时创建程序结束时销毁全局变量可以是外部的extern或静态的static 光说不练可不行来上陈年老代码 #include stdio.h
// 全局变量
int x 10;
int y 20;void func1()
{// 局部变量int x 100;printf(func1: x %d, y %d\n, x, y);
}void func2()
{// 局部变量int y 200;printf(func2: x %d, y %d\n, x, y);
}int main()
{printf(main: x %d, y %d\n, x, y);func1();func2();return 0;
}
/* 输出结果 */
//main: x 10, y 20
//func1: x 100, y 20
//func2: x 10, y 200 变量存储类型关键字 auto表示自动存储类别默认情况下所有局部变量都是auto类型默认就是可省略在变量前加auto修饰auto类型的局部变量存在于栈上每次函数调用时分配空间函数结束时释放空间 static表示静态存储类别可以修饰局部变量或全局变量static类型的局部变量存在于静态存储区在程序运行期间一直存在只能在定义它的函数内访问static类型的全局变量也存在于静态存储区在程序运行期间一直存在在定义它的文件内可访问 register表示寄存器存储类别只能修饰局部变量register类型的局部变量存在于寄存器中访问速度最快但数量有限register类型只是一个建议并不保证一定分配到寄存器中 extern表示外部链接存储类别默认情况下所有全局变量都是extern类型extern类型的全局变量存在于静态存储区在程序运行期间一直存在并且可以被其他文件引用和访问extern也可以用来声明一个在其他文件中已经定义好的全局变量 继续上实例 #include stdio.h// 全局静态变量
static int a 10;// 全局非静态变量
int b 20;// 外部函数声明
extern void func();int main()
{// 局部自动默认变量auto int c 30;// 局部寄存器请求变量register int d 40;// 局部静态改变变量static int e 50;printf(main: a %d, b %d, c %d, d %d, e %d\n, a, b, c, d, e);func();return 0;
}void func()
{// 引用全局非静态默认变量extern int b;// 定义全局非静态默认变量int f 60;printf(func: b %d, f %d\n, b, f);
} 输出结果
main: a 10, b 20, c 30, d 40, e 50
func: b 20, f 60 上面代码表示了在不同的文件和函数中不同存储类别修饰符影响了变量和函数的作用域和生命周期。 十五、令人费解的指针 C语言中指针是一种特殊的数据类型它可以存储一个变量的地址也就是变量在内存中的位置;通过指针我们可以直接操作内存中的数据而不需要知道变量的名字。 变量、内存、指针联系 我们要知道内存是什么内存是计算机中用来存储数据和指令的硬件设备它由许多个最小的存储单元组成每个单元都有一个唯一的编号称为地址我们可以把内存想象成一个巨大的柜子每个抽屉都有一个标签里面可以放东西 其次我们要知道变量是什么变量是程序中用来表示数据的标识符它有一个类型如int、char等一个名字如a、b等和一个值如10、c’等当我们定义一个变量时就相当于在内存中申请了一块空间来存放它的值并且给这块空间起了一个名字我们可以把变量想象成柜子里的抽屉上贴着名字的便利贴并且里面放着东西。 最后我们要知道指针是什么指针是一种特殊的变量它也有类型、名字和值但它的值不是普通的数据而是另一个变量或对象如数组、函数等所在内存单元的地址当我们定义一个指针时就相当于在内存中申请了一块空间来存放它所指向对象的地址并且给这块空间起了一个名字我们可以把指针想象成柜子里另外一种颜色的便利贴并且上面写着另外一张便利贴所在抽屉的标签号码。 指针基础概念 指针的基本概念有以下几点 指针变量的定义类型 *指针名;例如int *p; 指针变量的赋值指针名 变量名;例如p a;表示把变量a的地址赋给指针p 指针变量的解引用*指针名例如*p表示访问指针p所指向的内存单元中存储的数据 指针变量的运算指针可以进行加减运算但要注意运算结果仍然是一个地址而且与指针所指向的类型有关例如p 1表示把p所指向的地址加上一个整型数据所占用的字节数通常为4个字节 指针作为函数参数函数可以接收一个或多个指针作为参数这样可以实现在函数内部修改函数外部的变量或者传递大型数据结构如数组时提高效率 使用指针有以下几个意义 实现函数之间共享数据或者返回多个值 提高传参效率和节省内存空间 动态分配和管理堆内存 实现复杂的数据结构如链表、树、图等 指针的内存模型 指针的内存模型是指指针变量在内存中的存储方式和表示方法。 一般来说内存可以分为四个区域代码区、数据区、堆区和栈区代码区存放程序的指令数据区存放全局变量和静态变量堆区存放动态分配的内存栈区存放局部变量和函数调用时的参数。 指针变量本身也是一种变量它占用一定的内存空间通常为4个字节或8个字节并且有一个地址指针变量中存储的值是另一个变量的地址也就是说指针变量指向了另一个变量所在的内存单元。 例如 int a 10; // 定义一个整型变量a
int *p a; // 定义一个整型指针p并把a的地址赋给它 假设a占用4个字节p占用8个字节64位编译环境下在内存中可以表示为 地址值1000101004………200010002008… 其中1000是a所在的地址10是a的值2000是p所在的地址1000是p的值也就是a的地址2008是下一个内存单元的地址。 我们可以通过*p来访问或修改a所在内存单元中的值例如 printf(%d\n, *p); // 输出10
*p 20; // 修改a为20
printf(%d\n, a); // 输出20 我们还可以通过p来改变它所指向的对象例如 int b 30; // 定义另一个整型变量b
p b; // 把b的地址赋给p
printf(%d\n, *p); // 输出30 这时候在内存中可以表示为 地址值1000201004………1500301504………200015002008… 其中1500是b所在的地址30是b的值2000仍然是p所在的地址但现在它的值改为了1500也就是b 的地址*p现在访问或修改了b所在内存单元中的值。 各种不同类型指针 一些常见的指针类型有 基本类型指针如 int *、char *、float * 等用来指向基本类型的变量 数组指针如 int ()[10]、char ()[20] 等用来指向数组的首地址 指针数组如 int *[5]、char *[10] 等用来存储多个指针变量的数组 字符串指针如 char *str 或 char str[]用来指向字符串常量或字符数组 结构体指针如 struct student *stu用来指向结构体变量或结构体数组 函数指针如 void ()(int, char) 或 int ()(double)用来存储函数的地址可以通过函数指针调用函数 代码示例 #include stdio.h
#include stdlib.h// 整型指针
int *p1; // 声明一个整型指针变量 p1
int a 10; // 声明一个整型变量 a 并赋值为 10
p1 a; // 把 a 的地址赋值给 p1即 p1 指向 a
printf(The value of a is %d, the address of a is %p.\n, a, a); // 打印 a 的值和地址
printf(The value of *p1 is %d, the address of p1 is %p.\n, *p1, p1); // 打印 *p1 的值和 p1 的地址// 字符型指针
char *p2; // 声明一个字符型指针变量 p2
char b A; // 声明一个字符型变量 b 并赋值为 A
p2 b; // 把 b 的地址赋值给 p2即 p2 指向 b
printf(The value of b is %c, the address of b is %p.\n, b, b); // 打印 b 的值和地址
printf(The value of *p2 is %c, the address of p2 is %p.\n, *p2, p2); // 打印 *p2 的值和 p2 的地址// 数组指针
int (*p3)[3]; // 声明一个数组指针变量 p3它可以指向一个包含三个元素的整型数组
int c[3] {1, 2, 3}; // 声明一个包含三个元素的整型数组 c 并初始化为 {1, 2, 3}
p3 c; // 把 c 的地址赋值给 p3即 p3 指向 c
printf(The value of c[0] is %d, the address of c[0] is %p.\n, c[0], c[0]); // 打印 c[0] 的值和地址
printf(The value of (*p3)[0] is %d, the address of (*p3)[0] is %p.\n, (*p3)[0], (*p3)[0]); // 打印 (*p3)[0] 的值和地址// 函数指针
int add(int x, int y) { // 定义一个函数 add它接受两个整型参数 x 和 y并返回它们的和return x y;
}
int (*p4)(int x,int y); // 声明一个函数指针变量 p4它可以指向一个接受两个整型参数并返回整型结果的函数
// 注意声明函数指针时要与所要调用的函数保持一致参数类型、个数、顺序以及返回类型
// 可以使用 typedef 简化函数指针的声明例如typedef int (*func)(int x,int y);
// 这样就可以用 func 来代替 int (*func)(int x,int y)简化了代码。// 赋值方式一
// 直接将函数名赋给函数指针变量因为函数名就是该函数在内存中存放位置的首地址printf(\nThe first way to assign function pointer:\n);
printf(----------------------------------------\n);
printf(Address of function add: %#x\n,add);
printf(Address before assignment: %#x\n,*(add));p4 add; // 把 add 的地址赋值给 p4即 p4 指向 add
printf(Address after assignment: %#x\n,p4);
printf(Result of calling function add(3, 5) through its name: %d\n,add(3, 5)); // 直接通过函数名调用 add 函数
printf(Result of calling function add(3, 5) through pointer: %d\n,p4(3, 5)); // 通过函数指针变量 p4 调用 add 函数// 赋值方式二
// 使用 运算符取得函数的地址并赋给函数指针变量printf(\nThe second way to assign function pointer:\n);
printf(----------------------------------------\n);
printf(Address of function add: %#x\n,add);
printf(Address before assignment: %#x\n,*(add));
p4 add; // 把 add 的值赋值给 p4即 p4 指向 add
printf(Address after assignment: %#x\n,p4);
printf(Result of calling function add(3, 5) through its name: %d\n,add(3, 5)); // 直接通过函数名调用 add 函数
printf(Result of calling function (*p4)(3, 5) through pointer: %d\n,(*p4)(3, 5)); // 通过函数指针变量 p4 调用 add 函数注意要加括号 (*p4) 为什么需要指针 指针可以让你直接操作内存地址这样可以提高程序的效率和灵活性 指针可以实现函数的参数传递这样可以修改函数外部的变量或者传递大型的数据结构而不用复制 指针可以构建一些复杂的数据结构如链表、树、图等这些数据结构在很多问题中都有重要的应用 指针可以实现函数指针这样可以把函数作为参数传递给其他函数或者实现回调函数等功能 以上的代码实现必须用到指针仅用变量是不能实现的所以一个合格的C开发者必须要掌握指针编程一个必须用指针的实际例子是动态内存分配在操作系统环境的编程中会经常用到在程序运行过程中根据需要申请和释放内存空间这样可以节省内存资源提高程序的灵活性和效率。 C语言中动态内存分配的函数有 malloc、calloc、realloc 和 free。这些函数都需要使用指针来接收或传递申请或释放的内存地址例如 // 申请一个 int 类型的空间返回该空间的地址给 p
int *p (int *)malloc(sizeof(int));
// 判断是否申请成功
if (p NULL) {printf(Memory allocation failed.\n);exit(1);
}
// 给该空间赋值为 10
*p 10;
// 打印该空间的值和地址
printf(The value is %d, the address is %p.\n, *p, p);
// 释放该空间传递该空间的地址给 free
free(p); 多级指针 C语言的多级指针是指指向指针的指针也就是说它存储的是另一个指针变量的地址多级指针可以用来实现动态内存分配、链表、树等数据结构。 例如下面这段代码定义了一个二级指针p它可以用来修改一个一级指针q所指向的值 int x 10; // 定义一个整型变量x
int *q x; // 定义一个一级指针q让它存储x的地址
int **p q; // 定义一个二级指针p让它存储q的地址
**p 20; // 通过二级指针p修改x的值
printf(%d\n, x); // 输出20 根据二级指针的定义方式可以分为三种内存模型 指针数组char *arr[] {“abc”, “def”, “ghi”}; 这种模型定义了一个指针数组数组的每个元素都是一个地址指向一个字符串常量 二维数组char arr[3][5] {“abc”, “def”, “ghi”}; 这种模型定义了一个二维数组有3个5个char空间的存储变量 动态分配char **arr (char **)malloc(100 * sizeof(char *)); 这种模型定义了一个二级指针开辟了100个指针空间存放了100个地址 void*型指针 void *指针是一种特殊的指针类型可以存放任意对象的地址 void *指针没有确定的类型所以不能直接对其进行解引用或运算 void *指针可以通过强制类型转换来转换为其他类型的指针从而实现多态性 void *指针可以作为函数的参数或返回值表示不确定类型的数据 上栗子 #include stdio.h
//定义一个函数接受一个void *指针和一个int型变量根据变量的值来打印不同类型的数据
void printData(void *p, int type)
{if (type 0) //如果type为0表示p是int型指针{printf(The int value is %d\n, *(int *)p); //强制转换为int型指针并解引用打印}else if (type 1) //如果type为1表示p是char型指针{printf(The char value is %c\n, *(char *)p); //强制转换为char型指针并解引用打印}
}int main()
{int a 10; //定义一个int型变量achar b A; //定义一个char型变量bprintData(a, 0); //调用函数传入a的地址和0作为参数printData(b, 1); //调用函数传入b的地址和1作为参数return 0;
} 还有内容的续更中...敬请期待