IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言基础学习 -> 正文阅读

[C++知识库]C语言基础学习

一、C语言入门

1、软件安装

这里使用的是Visual C++2010

下载安装即可,建议不勾选任何东西,轻量运行

2、编写hello world

// 区分大小写,注意
// 我的第一个C程序
#include <stdio.h>  // 引入头文件,预编译

void main() {

	// 使用函数要引入头文件
	// printf是在<stdio.h>,需要引入该头文件
	printf("hello world");
	getchar();	// 让窗口停留
    // 每条语句都要以分号(;)结尾
}

3、运行过程

  1. 编码:首先编写文件代码,也就是hello.c源文件
  2. 编译:将hello.c文件翻译成目标文件(hello.obj),由cl.exe完成这个操作
  3. 链接:将目标文件(hello.obj)和库文件(系统提供的)进行链接生成可执行文件(hello.exe)由link.exe完成
  4. 运行:执行.exe文件,我们写好的程序就运行起来了

注意:①cl.exe和link.exe是在我们软件安装目录下的VS/bin目录下的

? ②我们的库文件它是由C程序提供的

? ③修改过的程序要重新编译内容才会发生改变

? ④编译到连接的过程中只要发生错误都会生成失败

文件存放的位置

  • E:\Work\VS2010\MyProject01\Debug下有一个MyProject01.exe文件
  • E:\Work\VS2010\MyProject01\MyProject01下有一个hello.c文件,这个就是我们的源文件
  • E:\Work\VS2010\MyProject01\MyProject01\Debug下有一个hello.obj是我们编译过的文件

4、换行符的使用

  • /t 制表符
  • /n 换行符
  • \\ 一个\
  • \" 一个"
  • \’ 一个’
  • \r 回车,将当前位置移到本行开头

代码实现

#include <stdio.h>  // 引入头文件,预编译

void main() {

	// 使用函数要引入头文件
	// printf是在<stdio.h>,需要引入该头文件
	// printf("hello world");

	// 转义字符的运用
	printf("小智\r很帅\n");
	printf("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京");

	getchar();	// 让窗口停留
}

5、注释

c语言有两种注释方式:

  1. 单行注释 //
  2. 多行注释 /**/

6、标准库的基本使用

#include <stdio.h>  // 引入头文件,预编译
#include <math.h>
#include <stdlib.h>

void main() {

	// 求2的三次幂
	double res = pow(2.0,3.0);
	printf("res=%2f",res);
	system("peuse");		// 函数表示暂停
}

二、变量

变量时程序的基本组成单位,它想相当于是内存中一个数据存储空间的表示

1、快速入门

#include <stdio.h>

void main() {
	int a = 1;
	double b = 1.1;
	char c = 'A';
	char name[] = "小智哥";	
	// 说明:%开头的为占位符
	// 输出整数%d
	// 输出浮点数%f
	// 输出字符%c
	// 输出字符串%s
	// 一定要一一对应,不然程序无法执行
	printf("num=%d source=%f.2f gender=%c name=%s", a, b, c, name);
	getchar();
}

image-20210720214444949

2.1 概念

变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个门牌号,通过门牌号找到我们的房间,而通过变量名可以访问到变量(值)

变量可以在声明的时候赋值,也可以先声明,后赋值,和java的一致

2、变量的数据类型

image-20210806204439512

注意:①在C语言中没有字符串类型,使用字符数组表示字符串

? ②在不同系统上,部分数据类型字节长度不一样,int2个字节或者4个字节

2.1 整型类型

C语言的整数类型就是用于存放整数值的,比如12 , 30, 3456等等

image-20210806204505411

整数使用的细节

  1. 各种类型的存储大小与操作系统、系统位数和编译器有关,目前通用的以64位的为主

    image-20210813202241111

  2. 在实际工作中,c程序通常运行在linux/unix操作系统下.二级考试,使用windows

  3. C 语言的整型类型,分为有符号signed 和无符号unsigned 两种,默认是signed

  4. C 程序中整型常声明为int型,除非不足以表示大数,才使用long或者long long

    #include <stdio.h>
    
    void main() {
    
    	long num1 = 12147483647;		// -737418241
    	long long num2 = 12147483647;	// 12147483647
    
    	// 如果输出的是long,则格式%ld 
    	// 如果输出的是long long,则格式%lld
    	printf("%lld", num2);
        // sizeof()方法可以获取数据类型的长度
        printf("\nlength=%d", sizeof(int));	// 4
    	getchar();
    }
    
  5. bit(位): 计算机中的最小存储单位。byte(字节):计 算机中基本存储单元。

    1byte = 8bit [二进制再详细说,简单举例一个short3 和int 3 ]

    示意图:

    short 3 在内存中占有2字节

    int3 在内存中占有 4个字节

    image-20210813205508888

2.2 浮点类型

C语言的浮点类型可以表示一一个小数,比如123.4,7.8,0.12等等

image-20210813210247038

注意:①关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位,浮点数是近视值

? ②尾数部分可能丢失,造成精度损失。

浮点型使用细节

  1. 浮点型常量默认为double型,声明float 型常量时,须后加‘f’ 或‘F’。

  2. 浮点型常量有两种表示形式

    十进制数形式:如: 5.12 512.0f .512 (必须有小数点)

    科学计数法形式:如: 5.12e2 、5. 12E-2

  3. 通常情况下,应该使用double型,因为它比float型更精确。

  4. printf(“d1=%f”, d1);//在输出时,默认保留小数点6位

代码实现

#include <stdio.h>

void main() 
{
	float d1 = 1.18923432;
	float d2 = 1.1f;
	double d3 = 1.3;

	double d4 = 5.12;
	double d5 = .512;		// 等价于0.512

	double d6 = 5.12e2;		// 等价于 5.12 * (10^2)=512
	double d7 = 5.12e-2;	// 等价于 5.12 * (10^-2)=5.12/100=0.0512

	// 在输出时,如果%f默认保留小数点后6位
	printf("d1=%.15f d2=%f d3=%f d4=%f d5=%f d6=%f d7=%f", d1, d2, d3, d4, d5, d6, d7);
	getchar();
}

image-20210813211611707

2.3 字符类型

字符类型可以表示单个字符,字符类型是char,char 是1个字节(可以存字母或者数字),多个字符称为字符串,在C语言中使用char数组表示,数组不是基本数据类型,而是构造类型[关于数组我们后面详细讲解.]

字符类型使用细节

  1. 字符常量是用单引号(")括起来的单个字符。例如: charcl = ‘a’; charc3= ‘9’;
  1. C中还允许使用转义字符‘\’ 来将其后的字符转变为特殊字符型常量。例如: charc3= ‘\n’ ; // "\n’表示换行符
  2. 在C中,char的本质是- 一个整数,在输出时,是ASCII码对应的字符。
  3. 可以直接给char赋一个整数,然后输出时,会按照对应的ASCII字符输出[97]
  4. char类型是可以进行运算的,相当于-一个整数,因为它都对应有Unicode码.

代码演示

#include <stdio.h>

void main()
{
	char c1 = 'a';
	char c2 = 97;
	// 这里会自动转换成int类型
	int sum = c1 + 10;	// 97 + 10 = 107

	// 如果是%c输出char类型的值的话就是本身
	// 如果是%d输出的话就会对照ASCLL编码表找到对应的数值输出
	printf("c1=%c c2=%c sum = %d", c1, c2, sum);
	getchar();

}

结果显示

image-20210813212637962

2.4 布尔类型

1)C语言标准(C89)没有定义布尔类型,所以C语言判断真假时以0为假,非0为真[案例]

2)但这种做法不直观,所以我们可以借助C语言的宏定义。

  1. C语言标准(C99)提供了_ Bool 型,Bool仍是整数类型,但与- -般整型不同的是, Bool变量只能赋值为0或1,非0的值都会被存储为1,C99 还提供了一个头文件<stdbool.h> 定义了bool 代表_ Bool, true 代表1,false 代表0。只要导入stdbool.h ,就能方便的操作布尔类型了,比如bool flag = false;[了解]

    ?条件控制语句; if

    ?循环控制语句; while …

代码演示

#include <stdio.h>

// 宏定义
#define BOOL int
#define TURE 1
#define FALSE 0

void main() {
	int isPass = -1;
	// 可以使用宏定义来完成
	// 定义一个布尔变量
	BOOL isOk = TURE;	// 等价于int isOK = 1
	if (isPass) {	// 0表示假的,1表示真的
		printf("通过考试");
	}
	if (isPass) {
		printf("ok");
	}
	int i = 1;
	int sum = i + TURE + FALSE;	// 2
	printf("sum=%d", sum);
	getchar();
}

2.5 基本数据类型转换

自动类型转换

image-20210816202523727

image-20210816202534685

1)有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度最大的那种数据类型,然后再进行计算(如int型和short型运算时,先把short转成int型后再进行运算)。

2)若两种类型的字节数不同,转换成字节数大的类型,若两种类型的字节数相同,且一种有符号,-种无符号,则转换成无符号类型

3)在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边的类型将转换为左边的类型,如果右边变量的数据类型长度比左边长时,将丢失一部分数据, 这样会降低精度,丢失的部分按四舍五入向前舍入

代码演示

#include <stdio.h>

void main() {

	char c1 = 'a';
	int num1 = c1;
	double d1 = num1;	// 它会自动类型提升

	float f1 = 2.2222f;
	double d2 = 4.2342342322;
	f1 = d2;	// 4.234234精度丢失

	printf("d1 = %.2f f1 = %f", d1, d2);
	getchar();
}

强制类型转换

将精度高的数据类型转换为精度小的数据类型。使用时要加上强制转换符(),但可能造成精度降低或溢出,格外要注意。

?强制类型转换一般格式如下:

? (类型名)表达式

? 什么是表达式:任何有 值都可以称为表达式,比如1+2, int num=2

? 这种强制类型转换操作并不改变操作数本身

要注意的是:不加强制转换符的是四舍五入,加了强转符就是直接截断小数点后面的

代码演示

#include <stdio.h>

void main() {

	double d1 = 2.34343;
	int num = (int)d1;	// 这里注意,它不是四舍五入,而是直接阶段小数点后的部分

	// 强制转换支队最近的数有效,如果希望针对更多的表达式转换,使用()
	int num2 = (int)(3.5 * 20 + 6 * 1.5);	// (int)(79)
	int num3 = (int)3.5 * 20 + 6 * 1.5;		// 60 + 9 = 69,所以要注意这个问题

	// d1依然是double
	printf("num=%d d1=%f num2=%d num3 = % d", num, d1, num2, num3);
	getchar();

}

3、指针入门

简单的来说,指正表示一个地址(存放的就是一个地址)

写法:int* ptr或者int *ptr都可以

image-20210816205701239

注意:指针的类型一定要和数据类型是一致的,int类型的指针只能存放int类型的地址

代码演示

#include <stdio.h>

void main() {

	int num = 1;
	// 定义一个指针变量
	// 说明
	// 1 int*表示类型为指针类型
	// 2 名称ptr,ptr就是一个int*类型
	// 3 ptr指向一个int类型的变量的地址
	int* ptr = &num;

	// 说明1:如果要取出一个变量的地址,使用格式是%p
	// 说明2: &num表示取出num这个变量对应地址
	printf("num的值=%d num地址=%p", num, ptr);	// num的值=1 num地址=00BCFE6C

	// 1 指针变量,本身也有地址	&ptr
	// 2 指针变量,存放的地址	ptr
	// 3 获取指针指向的值	*ptr
 
	//ptr的地址是006FFA9C ptr存放的值为006FFAA8 ptr指向的值=1
	printf("\nptr的地址是%p ptr存放的值为%p ptr指向的值=%d", &ptr, ptr, *ptr);

	// 修改num的值
	*ptr = 99;
	printf("\nptr指向的值=%d", *ptr);	// 99

	float a = 22;
	float* ptr2 = &a;
	
	double sum = *ptr + *ptr2;
	printf("\nsum=%.2f", sum);	// 99

	getchar();
}

4、值传递和地址传递

C 语言传递参数(或者赋值)可以是值传递(pass by value), 也可以传递指针(a pointer passedbyvalue),传递指针也叫地址传递。

  1. 默认传递值的类型:基本数据类型(整型类型、小数类型,字符类型),结构体,共用体。
  2. 默认 传递地址的类似:指针、数组

值传递和地址传递使用特点

1)值传递:将变量指向的存储内容,在传递/赋值时,拷贝- -份给接收变量.

2)地址传递也叫指针传递:如果是指针,就将指针变量存储的地址,传递给接收变量,如果是数组,就将数组的首地址传递给接收变量。

image-20210816212925366

代码演示

#include <stdio.h>

void main() {

	int num = 100;
	int* p = &num;
	int* p2 = p;
	*p2 = 66;
	printf("num=%d", num);	// 66

	getchar();
}

三、常量

3.1 基本介绍

1)常量是固定值, 在程序执行期间不能改变。这些固定的值,又叫做字面量

2)常 量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。

3)常量的值在定义后不能进行修改.

3.2 经常使用的常量

1 整数常量

1)整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数: 0x或0X表示十六进制,0表示八进制,不带前缀则默认表示十进制。整数常量也可以带一个后缀,后缀是U和L的组合,∪表示无符号整数( unsigned),L表示长整数(long)。后缀可以是大写,也可以是小写,U和L的顺序任意

2)整数常量举例

image-20210818200831547

int n1 = 0213;	// 八进制
int n2 = 0x4b;	// 十六进制

2 浮点常量

image-20210818200924634

char c1 = 'a';	
char c2 = '\t';		//'\t'是字符常量

3 字符常量

char c1 = 'a';
char c2 = '\t';

4 字符串常量

char str1[20] = "我是靓仔";
char str2[30] = "春暖花开,世界和平";

3.3 常量的定义

1 定义常量的两种方式

① 使用#define预处理器

② 使用const关键字

2 #define预处理器

形式:#define 常量名 常量值

代码演示

#include <stdio.h>

#define PI 3.14	// 定义常量PI 常量值为3.14
void main() {

	//PI = 3.1415926;	常量不能被修改

	double area;

	double r = 1.2;	// 半径
	area = PI * r * r;
	printf("面积:%.2f", area);
	getchar();
}

3 const关键字

#include <stdio.h>

const double PI = 3.14;		// 类似于java的final关键字
void main() {

	double area;
	double r = 1.2;	// 半径
	area = PI * r * r;
	printf("面积:%.2f", area);
	getchar();
}

4 const和#define的区别

  1. const 定义的常量时,带类型,define 不带类型
  2. const是在编译、运行的时候起作用,而define是在编译的预处理阶段起作用
  3. define 只是简单的替换,没有类型检查。简单的字符串替换会导致边界效应 [案例演示].
  4. const常量可以进行调试的,define是不能进行调试的,主要是预编译阶段就已经替换掉了,调试的时候就没它了
  5. const 不能重定义,不可以定义两个一样的,而define通过undef取消某个符号的定义,再重新定义[案例]
  6. define 可以配合#ifdef、#ifndef、 #endif 来使用,可 以让代码更加灵活,比如我们可以通过#define来启动或者关闭调试信息。[案例]

案例

#define A 1;
#define B A + 3;
#define B2 (A + 3)
// define只是简单地替换,直接换
#define C A/B*3;	// 相当于是1/1+3*3 = 10
#define C2 A/B*3	// 相当于是1/(1+3)*3=0.75
#include <stdio.h>

#define DEBUG

void main() {

#ifdef DEBUG
	printf("ok, 调试信息");
#endif
#ifdef DEBUG
	printf("hello,另外的信息");
#endif

#undef DEBUG	// 取消DEBUG的定义
#define DEBUG 123	// 重新定义DEBUG
	printf("DEBUG=%d", DEBUG);
	getchar();
}

四、运算符

1 算数运算符

image-20210822154820131

代码演示

#include <stdio.h>

void main() {

	int i = 10;
	int j = 3;
	int sum = i / j;	// sum = 3

	double i2 = 10;
	int j2 = 4;
	double sum2 = i2 / j2;	// sum2 = 2.50

	printf("sum=%d,sum2=%.2f", sum, sum2);
	getchar();
}

注意事项

对于除号“1”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分

? 例如: intx=10/3,结果是3

标识符命名规范

1)程序中不得出现仅靠大小写区分的相似的标识符,intx,X;变量x与X容易混淆

2)所有宏定义、 枚举常数、常量(只读变量)全用大写字母命名,用下划线分隔单词

? 比如:const double TAX RATE = 0.08; //TAX RATE只读变量

? #define FILE_ PATH “/usr/tmp”

3)定义变量别忘了初始化。定义变量时编译器并不一定清空了这块内存,它的值可能是无效的数据,运行程序,会异常退出.

4)变量名、 函数名:多单词组成时,第-一个单词 首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz [驼峰法,小驼峰,比如short stuAge = 20;]

? 比如:tankShotGame tankShotGame

关键字

image-20210822161058806

键盘输入语句

scanf,类似于java的scanner,控制台输入

五、二进制和位运算符

进制

对于整数,有四种表示方式:

  1. 二进制: 0,1,满2进1,C语言中没有二进制常数的表示方法。

  2. 2)十进制: 0-9 ,满10进1。

  3. 3)八进制: 0-7,满8进1.以数字0开头表示。

  4. 十六进制: 0-9及A-F,满16进1.以0x或0X开头表示。此处的A-F不区分大小写。[A->10B->11C->12D->13E->14 F->15 ]

    如: 0x21AF +1= 0X21B0

?举例说明:

int num2 = 210; //十进制
int num3 = 01010; //八进制
int num4 = 0x1010;//十六进制

进制的图示

image-20210822165826536

image-20210822165838023

进制转换

1 其他进制转换成十进制

①二进制转十进制

规则:从最低位开始,将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和。

案例:将二进制1011转成十进制的数

1011 = 1 * 2^0 + 1 * 2^1 + 0 * 1^2 + 1 * 2^3 = 1 + 2 + 0 + 8 = 11

②八进制转十进制

规则:从最低位开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和。

案例:将0123转成十进制的数

0123 = 3 * 8^0 + 2 * 8^1 + 1 * 8^2 = 3 + 16 + 64 = 83

③十六进制转十进制

规则:从最低位开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和

案例:将0X34A转成十进制的数

0X34A = 10 * 16^0 + 4 * 16^1 + 3 * 16^2 = 10 + 64 + 768 = 842

课后练习

110001100转成干进制

2^2 + 2^3 + 2^7 + 2^8 = 4 + 8 + 128 + 256 = 396

02456转成十进制

6 * 8^0 + 5 * 8^1 + 4 * 8^2 + 2 * 8^3 = 6 + 40 + 256 + 1024 = 1326

0xA45转成十进制

5 * 16^0 + 4 * 16^1 + 10 * 16^2 = 5 + 64 + 2560 = 2629

2 十进制转其他进制

①十进制转二进制

规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制。

案例:将56转成二进制

image-20210822172658171

②十进制转八进制

规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的八进制。

案例:请将156转成八进制

234

③十进制转十六进制

规则:将该数不断除以16,直到南为0为止,然后将每步得到的余数倒过来,就是对应的十六进制。

案例:请将356转成十六进制

164

课堂练习

image-20210822172901400

123 -> 0111 1011

678 -> 01246

8912 -> 0X22D0

3 二进制转八进制和十六进制

①二转八

规则:从低位开始,将=二进制数每三位一组, 转成对应的八进制数即可。

案例:请将11010101转成八进制

11 010 101 -> 0325

②二转十六

规则:低位开始,将二进制数每四位一组, 转成对应的十六进制数即可。

案例:请将11010101转成十六进制

1101 0101 -> 0XD5

练习

image-20210822174855460

11 100 101 -> 0345

11 1001 0110 -> 0X396

4 八进制和十六进制转二进制

①八转二

规则:将八进制数每1位,转成对应的一一个3位的二进制数即可。

案例:请将0237转成二进制

0237 -> 01 011 111

②十六转二

规则:将十六进制数每1位,转成对应的4位的-一个二进制数即可。

案例:请将0x23B转成二进制

0x23B -> 0010 0011 1011

练习

image-20210824213455239

01230 -> 1 010 011 000

0XAB29 -> 1010 1011 0010 1001

原码、反码、补码

必须要记住,很重要

image-20210825200833011

这里演示用四个字节
正数三码合一
2
原码: 00000000 00000000 00000000 00000010
反码: 00000000 00000000 00000000 00000010
补码:	00000000 00000000 00000000 00000010
    
-2
原码: 10000000 00000000 00000000 00000010
反码: 11111111 11111111 11111111 11111101
补码: 11111111 11111111 11111111 11111110

位运算符

image-20210825195950914

示例

image-20210825200035242

~2

~2
2的补码: 00000000 00000000 00000000 00000010

补码取反: 11111111 11111111 11111111 11111101
然后将上面取反的补码转成原码就是我们的结果了

最高符号位为1,它是一个负数,所以要按照负数的规则来转
思路:先转成反码,反码再转成原码
转反码 = 补码 - 1
-> 11111111 11111111 11111111 11111100
反码 -> 原码
-> 10000000 00000000 00000000 00000011
所以~2的结果是 -3

~-5

~-5
-5的原码 -> 10000000 00000000 00000000 00000101
    反码 -> 01111111 11111111 11111111 11111010
    补码 -> 01111111 11111111 11111111 11111011
    
    取反 -> 10000000 00000000 00000000 00000100
    
    反码 -> 01111111 11111111 11111111 11111011
    原码 -> 00000000 00000000 00000000 00000100
结果是 4

2&-3

2&-3
-3的原码: 10000000 00000000 00000000 00000011
    反码: 11111111 11111111 11111111 11111100
    补码: 11111111 11111111 11111111 11111101
 2的补码: 00000000 00000000 00000000 00000010
      & : 00000000 00000000 00000000 00000000
    结果为0

2|3

 2|3
  2的补码: 00000000 00000000 00000000 00000010
  3的补码: 00000000 00000000 00000000 00000011
       | : 00000000 00000000 00000000 00000011
       结果为3

2^3

2^3
  2的补码: 00000000 00000000 00000000 00000010
  3的补码: 00000000 00000000 00000000 00000011
       ^ : 00000000 00000000 00000000 00000001
      结果为1

4&-5

-5的原码: 10000000 00000000 00000000 00000101
    反码: 11111111 11111111 11111111 11111010
    补码: 11111111 11111111 11111111 11111011
  4的补码:00000000 00000000 00000000 00000100
      & : 00000000 00000000 00000000 00000000
          结果为0

左移和右移

image-20210825215019063

正数的右移就是除以2,又移几位就是除于几个2,左移就是乘于2,左移几位就是乘于几个2

负数的要通过推

举例子

2的补码: 00000000 00000000 00000000 00000010
   2>>1: 00000000 00000000 00000000 00000001
   就是整体向右移动一位,符号位为0,所以用符号位补全溢出的高位

-1 >> 2

-1的原码: 10000000 00000000 00000000 00000001
    反码: 11111111 11111111 11111111 11111110
    补码: 11111111 11111111 11111111 11111111
   -1>>2: 11111111 11111111 11111111 11111111
    反码: 11111111 11111111 11111111 11111110
    原码: 10000000 00000000 00000000 00000001
        结果是-1

-1 << 2

-1的补码: 11111111 11111111 11111111 11111111
   -1<<2: 11111111 11111111 11111111 11111100
    反码: 11111111 11111111 11111111 11111011
    原码: 10000000 00000000 00000000 00000100
        结果为-4

六、程序流程控制

switch要注意的细节

image-20210827162347236

goto

类似于水门的飞雷神,通过标志然后进行传送

image-20210827151853517

image-20210827151907129

#include <stdio.h>

void main() {
	printf("start\n");
	goto lablel; //lable1称为标签
	printf("ok l\n");
	printf("ok2\n");
	lablel:
	printf("ok3\n");
	printf("ok4\n");
		getchar();
} //输出 ok3和ok4

练习

水仙花数

image-20210827152946275

#include <stdio.h>

void main() {

	int num = 154;	// 不是水仙花数
	int num1 = num / 100;		// 百位数
	int num2 = num % 100 / 10;	// 十位数
	int num3 = num % 10;		// 个位数
	if (num == num1 * num1 * num1 + num2 * num2 * num2 + num3 * num3 * num3) {
		printf("%d是水仙花数",num);
	}
	else {
		printf("%d不是水仙花数", num);
	}
	getchar();
}

求天数

image-20210827153826637

#include <stdio.h>

// 求月的天数,要考虑闰年和平年
// 思路:31天的月份和30的月份区分开,还要判断是否是闰年

void main () {
	// 31天的月份分别是:1月、3月、5月、7月、8月、10月、12月
	int year = 2021;
	int month = 2;

	switch (month)
	{
	case 1:
	case 3:
	case 5:
	case 7:
	case 8:
	case 10:
	case 12:
		printf("%d年的%d月份是%d天", year, month, 31);
		break;
	case 2:
		// 判断是否是闰年
		if (year % 4 == 0 && year / 100 != 0) {
			printf("%d年的%d月份是%d天", year, month, 29);
		}
		printf("%d年的%d月份是%d天", year, month, 28);
		break;
	default:
		printf("%d年的%d月份是%d天", year, month, 30);
		break;
	}
	getchar();
}

判断星期

image-20210827155233914

#include <stdio.h>

// 判断星期,星期一到星期三,打印AAA,星期四到星期五打印BBB,星期六到星期日打印CCC

void main() {
	int week = 1;
    
	switch (week)
	{
	case 1:
	case 2:
	case 3:
		printf("AAA");
		break;
	case 4:
	case 5:
		printf("BBB");
		break;
	case 6:
	case 7:
		printf("CCC");
		break;
	}
	getchar();
}

输出a-z和A-Z

image-20210827155839298

#include <stdio.h>

void main() {

	for (char c1 = 'a'; c1 <= 'z'; c1++)
	{
		printf("%c\n", c1);
	}
	for (char c2 = 'A'; c2 <= 'Z'; c2++)
	{
		printf("%c\n", c2);
	}
	getchar();
}

七、枚举

  1. 枚举是C语言中的一种构造数据类型,它可以让数据更简洁,更易读,对于只有几个有限的特定数据,可以
    使用枚举.

  2. 枚举对应英文(enumeration,简写enum)

  3. 枚举是一组常量的集合,包含- -组有限的特定的数据

  4. 枚举语法定义格式为

    enum
    枚举名{枚举元素 1,枚举元素...;.
    

快速入门

#include <stdio.h>

// 枚举快速入门
enum DAY
{
	MON = 1,TUE = 2, WED = 3, THU = 4, FRI = 5, SAT = 6,SUN = 7
};

void main() {
	// 也可以在方法内部定义
	//enum DAY
	//{
	//	MON = 1, TUE = 2, WED = 3, THU = 4, FRI = 5, SAT = 6, SUN = 7
	//};
	enum DAY day = WED;
	printf("%d", day);
	getchar();
}

遍历枚举

for循环

#include <stdio.h>

// for循环遍历枚举
enum DAY 
{
	// 如果没有赋值,就会按照顺序赋值,如果第一个也没有赋值,那么就会从0开始赋值
	MON , TUE, WED, THU, FRI, SAT, SUN	
}day;	// 表示定义了一个枚举类型,同时定义了一个变量day(enum DAY类型)
void main() {
	for (day = MON; day <= SUN; day++) {
		printf("%d\n", day);
	}
	getchar();
}

switch循环

#include <stdio.h>

enum SEASONS
{
	SPRING=1, SUNMMER, AUTUMN, WINTER
}season;	// 定义枚举类型,变量名season

void main() {
	printf("请输入你喜欢的季节:(1.spring 2.summer 3.autumn 4.winter):");
	scanf_s("%d", &season);
	switch (season)
	{
	case SPRING:
		printf("你喜欢的是春天");
		break;
	case SUNMMER:
		printf("你喜欢的是夏天");
		break;
	case AUTUMN:
		printf("你喜欢的是秋天");
		break;
	case WINTER:
		printf("你喜欢的是的冬天");
		break;
	default:
		printf("没有你喜欢的天");
		break;
	}
	getchar();
	getchar();
}

枚举的注意事项和细节

  1. 第一个枚举成员的默认值为整型的0, 后续枚举成员的值在前一个成员上加1。 我们在这个实例中把第一一个枚
    举成员的值定义为1,第二个就为2,以此类推. [看案例]

  2. 在定义枚举类型时改变枚举的元素的值

    enum DAY {
    	MON, TUE, WED, THU=9, FRI, SAT, SUN //如果没有给赋值,就会按照顺序赋值.
    } day;  // 表示定义了一- 个枚举类型enum Day,同时定义了一个变量day(类型是enum DAY)
    // MON, TUE, WED 为 0 , 1 , 2
    //说明FRI, SAT, SUN就是10, 11, 12
    

    所以后面数的赋值要根据前面数来定

  3. 枚举变量的定义也可以先定义枚举类型,再定义枚举变量

    enum DAY {
    MON=1, TUE, WED, THU, FRI, SAT, SUN
    };
    enum DAY day; 
    
  4. 枚举变量的定义可以省略枚举名称,直接定义枚举变量

    enum {
    	MON=1, TUE, WED, THU, FRI, SAT, SUN
    } day;	// 这样使用枚举,该枚举类型只能使用- - -次.
    

    注意:这种定义只能用一次

  5. 可以将整数转换成对应的枚举值

    #include <stdio.h>
    int main() {
    	enum SEASONS { SPRING = 1, SUMMER, AUTUMN, WINTER };
    	enum SEASONS season;
    	int n = 4;
    	season = (enum SEASONS)n;	// 转换成枚举值
    	printf("season = %d", season);
    		getchar();
    	return 0;
    }
    

八、函数

头文件概述

  1. 头文件是扩展名为.h的文件,包含了C函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:
    程序员编写的头文件和C标准库自带的头文件
  2. 在程序中要使用头文件, 需要使用C预处理指令#include来引用它。前面我们已经看过stdio.h 头文件,它是
    C标准库自带的头文件
  3. #include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include也是C语言预处理命令的一-种。#include
    的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成-一个源
    文件,这与复制粘贴的效果相同。但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错,
    特别在程序是由多个源文件组成的时候。
  4. 建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件

代码实现

首先我们要定义头文件,定义好头文件之后我们就编写对应函数的代码

在头文件文件夹中创建 myfun.h 文件
int sum(int a, int b);

void sayHello();

编写对应的代码

#include <stdio.h>

int sum(int a, int b) {
	return a + b;
}

void sayHello() {
	printf("hello");
}

测试函数

#include <stdio.h>
#include "myfun.h"	// 引入自定义的函数

void main() {
	int a = 2;
	int b = 3;
	int s = sum(a, b);
	printf("%d\n", s);
	sayHello();		// 调用sayHello方法
	getchar();
}

头文件的注意事项和细节说明

  1. 引用头文件相当于复制头文件的内容

  2. 源文件的名字可以不和头文件一样,但是为了好管理,一**-般头文件名和源文件名一样.**

  3. C语言中include<> 与include ""的区别
    include <>:引用的是编译器的类库路径里面的头文件,用于引用系统头文件。
    include"":引用的是你程序目录的相对路径中的头文件,如果在程序目录没有找到引用的头文件则到编译器的
    类库路径的目录下找该头文件,用于引用用户头文件。
    所以:
    *引用系统头文件,两种形式都会可以,include<> 效率高
    *引用用户头文件,只能使用include " "

:" "它会先去找自定义的函数,找不到自定义的就会去找系统提供的

  1. 一个#include 命令只能包含一个头文件,多个头文件需要多个#include 命令

  2. 同一个头文件如果被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引
    入的机制

  3. 在一个被包含的文件(.c)中又可以包含另一个文件头文件(.h)

  4. 不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会
    引起重复定义错误(!!!

因为include是替换头文件里面的内容,如果是多次引入函数的定义的话就没事,因为它有查重的机制,如果是多次引入函数的声明的话就会出问题。

函数的调用过程

函数的调用规则(适用于java,c++,php)

  1. 当调用(执行)一个函数时,就会开辟一个独立的空间(栈)

  2. 每个栈空间都是相互独立的

  3. 当函数执行完毕后(或者执行到return语句),会返回调用函数的位置,继续执行下面的代码

  4. 如果函数有返回值,则将返回值赋给接收的变量

    ①如果方法返回的数据是double类型的,我们可以ruturn比他小的类型,比如int类型,它会自动类型提升到double类型

    ②如果方法返回的数据是char类型的,return 一个int类型就会报错,需要进行强制类型转换才能return

  5. 当一个函数返回后,该函数对应的栈空间也就销毁了

image-20210828164211734

举例说明

image-20210828163647936

函数-递归调用

我调我自己,在方法内部调用自己

#include <stdio.h>

void test(int n) {
	if (n > 2) {
		test(n - 1);
	}
	printf("n=%d\n", n);
}
void main() {
	test(4);
	getchar();
}

image-20210828170422426

函数递归需要遵守的重要规则

  1. 执行一个函数时,就创建一个独立的空间(栈)
  2. 函数的局部变量时独立的,不会相互影响
  3. 递归必须有退出递归的条件,如果没有就会无限递归,造成内存溢出
  4. 当一个函数执行完毕,或遇到return,就会返回,谁调用那么结果就返回给谁

递归练习题

?题1: 斐波那契数
请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13…
给你-一个整数n,求出它的斐波那契数是多少?

#include <stdio.h>

int fbn(int n) {
	if (n == 1 | n == 2) {
		return 1;
	}
	else {
		return fbn(n - 1) + fbn(n - 2);
	}
}

void main() {

	// 求斐波那契数
	int res = fbn(5);
	printf("res=%d", res);
	getchar();
}

?题2:求函数值
已知f(1)=3; f(n)= 2*f(n-1)+1;
请使用递归的思想编程,求出f(n)的值?

#include <stdio.h>

int f(int n) {
	if (n == 1) {
		return 3;
	}
	else {
		return 2 * f(n - 1) + 1;
	}
}

void main() {
	// 求函数值
	int res = f(7);
	printf("res=%d", res);
	getchar();
}

?题3:猴子吃桃子问题
有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一-半,然后再多吃一一个。
当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?

#include <stdio.h>
// 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一 - 半,然后再多吃一一个。
// 当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子 ?
int peach(int day) {
	if (day == 10) {
		return 1;
	}
	else {
		return (peach(day + 1) + 1) * 2;
	}
}

void main() {
	// 猴子吃桃子问题
	int res = peach(1);
	printf("res=%d", res);
	getchar();
}

函数的注意事项和细节讨论

  1. 函数的形参列表可以是多个。

  2. C 语言传递参数可以是值传递(pass by value),也可以传递指针(a pointer passed by value)也叫引用传递。

  3. 函数的命名遵循标识符命名规范,首字母不能是数字,可以采用驼峰法或者下划线法,比如getMax()
    get_ max().

  4. 函数中的变量是局部的, 函数外不生效

  5. 基本数据类型默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

  6. 如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果
    上看类似引用(即传递指针) [ 案例演示:]

    #include <stdio.h>
    void test(int* p) {
    	(*p)++;
    }
    
    void main() {
    	int p = 3;
    	test(&p);
    	printf("p=%d", p);
    	getchar();
    }
    
  7. C语言不支持函数重载。

  8. C语言支持可变参数函数1/知道即可[案例演示]

    #include <stdio.h>
    #include <stdarg.h>
    
    void test(int* p) {
    	(*p)++;
    }
    int fun(int num, ...) {		// 可变形参就是将一堆参数放入到一个数组中
    	int i, totalSum = 0;	// totalSum一定要初始化
    	int val = 0;
    	va_list v1;
    	va_start(v1, num);
    	printf("*v=%d\n", v1);
    	for (i = 0 ; i < num; i++)
    	{
    		val = va_arg(v1, int);
    		printf("val=%d\n", val);
    		totalSum += val;
    	}
    	va_end(v1);
    	return totalSum;
    }
    
    void main() {
    	// 传入指针参数
    	//int p = 3;
    	//test(&p);
    	//printf("p=%d", p);
    
    	// 可变形参
    	int res = fun(8, 1, 2, 3, 4, 5, 5, 6, 9);	// 第一个参数是它这个可变形参数组的长度
    	printf("res=%d", res);
    	getchar();
    }
    

练习题

请编写一个函数swap(intnl, int **n2) 可以交换nl和:n2的值

#include <stdio.h>

void swap(int *n1, int *n2) {
	int temp = *n1;
	*n1 = *n2;
	*n2 = temp;
}

void main() {
	int n1 = 2;
	int n2 = 3;
	swap(&n1, &n2);
	printf("n1=%d, n2=%d", n1, n2);
	getchar();
}

函数参数的传递方式

两种传递方式

  1. 值传递
  2. 引用传递(传递指针、地址)

值传递和引用传递的特点

  • 值传递:变量直接存储值,内存通常在栈中分配

    默认是值传递的数据类型有: 1.基本数据类型2. 结构体3.共用体4.枚举类型

  • 引用传递:变量存储的是地址,这个地址对应的空间才是具体的值

    默认是引用传递的数据类型有: 指针和数组

如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量(*指针)。

变量的作用域

变量作用域就是指变量的有效范围

  1. 函数内部声明/定义的局部变量,作用域仅限于函数内部。
  2. 函数的参数, 形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用局部变量(编译器
    使用就近原则)
  3. 在一个代码块,比如for/if中的局部变量,那么这个变量的的作用域就在该代码块
  4. 4)在所有 函数外部定义的变量叫全局变量,作用域在整个程序有效

初始化局部变量和全局变量

  1. 局部变量,系统不会对其默认初始化,必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出.

  2. 全局变量,系统会自动对其初始化,如下所示

    image-20210829092943836

  3. 正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量
    会导致一些在内存位置中已经可用的垃圾值

作用域的注意事项和细节

  1. 全局变量(Global Variable)保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也
    就是所有的代码文件,包括源文件(.c 文件)和头文件(.h 文件)。[c 程序内存布局图!!!]

    image-20210829094611925

  2. 局部变量(IocalVariable)保存在栈中,函数被调用时才动态地为变量分配存储单元,它的作用域仅限于函数内
    部。[内存布局分析]

  3. C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量

  4. 在同一个作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用时编译器采用就近原则.

  5. 由{ }包围的代码块也拥有独立的作用域

练习

思考:下面的代码输 出什么内容?

#include <stdio.h>

double price = 200.0;	// 全局变量

void test01() {
	printf("%.2f\n", price);
}
void test02() {
	// 编译器采用就近原则
	double price = 250.0;
	printf("%.2f\n", price);
}

void main() {
	printf("main price = %.2f\n", price);
	test01();	// 200
	test02();	// 250
	test01();	// 200	因为调用test02的时候已经修改的是局部变量price的值,所以并没有修改全局变量
	getchar();
}

思考:下面的代码输 出什么内容?

#include <stdio.h>

int n = 10;

void fun1() {
	int n = 20;
	printf("fun1 n:%d\n", n);	// 局部变量
}

void fun2(int n) {
	printf("fun2 n:%d\n", n);	// 形参n
}

void fun3() {
	printf("fun3 n:%d\n", n);	// 全局变量n
}

void main() {
	int n = 30;
	fun1();		// 20
	fun2(n);	// 30
	fun3();		// 10
	getchar();
}

static关键字

c语言的static和java的是相反的,它和java中的private关键字的作用一样

局部变量使用static修饰

  1. 局部变量被static 修饰后,我们称为静态局部变量
  2. 对应静态局部变量在声明时未赋初值,编译器也会把它初始化为0。
  3. 静态局部变量存储于进程的静态存储区(全局性质),只会被初始一-次,即使函数返回,它的值也会保持不变

代码演示

void main() {
	static int n;
	printf("n=%d", n);	// 0
	getchar();
}
#include <stdio.h>

//void main() {
//	static int n;
//	printf("n=%d", n);	// 0
//	getchar();
//}

void fun(void) {	// void就是告诉编译器没有形参,可写可不写
	int n = 10;
	printf("n=%d\n", n);
	n++;
	printf("n++=%d\n", n);
}

void fun_static(void) {
	static int n = 10;
	printf("\nstatic n=%d\n", n);
	n++;
	printf("\nn++=%d\n", n);
}

void main() {
	fun();
	printf("-----------\n");
	fun_static();
	printf("-----------\n");
	fun();
	printf("-----------\n");
	fun_static();
	getchar();
}

全局变量使用static修饰

  1. 普通全局变量对整个工程可见,其他文件可以使用exterm外部声明后直接使用。也就是说其他文件不能再定义
    一个 与其相同名字的变量了( 否则编译器会认为它们是同一个变量),静态全局变量仅对当前文件可见,其他
    文件不可访问,其他文件可以定义与其同名的变量,两者互不影响[案例]
file01
int n = 10;
static int n2 = 20;
file02
extern int n2;
// extern int n;
void main() {
    //printf("%d", n);	// 10
	printf("%d", n2);
	getchar();
}

image-20210829104215962

  1. 定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同
    文件同名变量的冲突,且不会误使用

函数用static修饰

和修饰全局变量一个作用,都是只能在本文件中使用,其他文件不能使用

  1. 函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数

  2. 非静态函数可以在另一个文件中通过extern 引用[ 案例]

  3. 静态函数只能在声明它的文件中可见,其他文件不能引用该函数[案例]

  4. 不同的文件 可以使用相同名字的静态函数,互不影响[案例]

    如果引用了其他文件的全局变量,那么就不能定义和这个相同的全局变量了

代码演示

file03
#include <stdio.h>

void fun1() {
	printf("我是靓仔智");
}

static void fun2() {
	printf("我是傻逼智");
}
file04
#include <stdio.h>

extern void fun1();
// extern void fun2();

void main() {
	//fun1();
	fun2();
	getchar();
}

image-20210829105442480

常用的系统函数

字符串常用的系统函数

  1. 得到字符串的长度
    size_ t strlen( const char *str)
    计算字符串str 的长度,直到空结束字符,但不包括空结束字符。
  2. 拷贝字符串
    char *strcpy(char *dest, const char *src)
    把SrC所指向的字符串复制到dest。
  3. 连接字符串(类似java的append)
    char *strcat( char * dest, const char *src)
    把src所指向的字符串追加到dest所指向的字符串的结尾。

代码演示

#include <stdio.h>
#include <string.h>		// 使用字符串要引入对应的函数库

// 测试常用的字符串函数
void main() {
	char str[30] = "abcde";
	char a[20] = "asd";
	char b[30] = "世界上最靓仔的是";
	int length = strlen(str);
	strcpy(a, "cccc");
	strcat(b, "靓仔智");
	printf("length=%d\n", length);
	printf("a=%s\n", a);	// cccc,覆盖了原来的数据
	printf("b=%s\n", b);	// 世界上最靓仔的是靓仔智
	getchar();
}

时间和日期相关函数

  1. 获取当前时间
    char *ctime( const time t *timer)

    返回一个表示当地时间的字符串,当地时间是基于参数timer。

    void main() {
    	time_t curtime;	// time_h是一个结构体类型
    	time(&curtime);	// time()完成初始化
    	// ctime返回一个表示当前时间的字符串,当前时间是基于参数timer
    	printf("当前时间:%s", ctime(&curtime));
    	getchar();
    }
    
  2. 编写一段代码来统计函数test执行的时间
    double difftime(time_ t timel, time_ _t time2)
    返回timel 和time2之间相差的秒数(timel-time2)。 .

    #include <stdio.h>
    #include <time.h>
    
    void test() {
    	printf("test函数开始执行\n");
    	int sum = 0;
    	for (int i = 0; i < 66666; i++)
    	{
    		sum = 0;
    		for (int j = 0; j < 10; j++)
    		{
    			sum += j;
    			printf("%d", i);
    		}
    
    	}
    }
    void main() {
    	//time_t curtime;	// time_h是一个结构体类型
    	//time(&curtime);	// time()完成初始化
    	 ctime返回一个表示当前时间的字符串,当前时间是基于参数timer
    	//printf("当前时间:%s", ctime(&curtime));
    	//getchar();
    
    	// 执行test()前的时间
    	time_t start_t, end_t;
    	double diff_t;	// 存放时间差
    	printf("程序启动\n");
    	time(&start_t);	// 初始化当前时间
    
    	test();	// 执行test
    
    	// 执行test()后的时间
    	time(&end_t);	// 得到当前时间
    	diff_t = difftime(end_t, start_t);	// 时间差,结束时间-开始时间
    	printf("执行test函数耗用了%.2f秒", diff_t);
    	getchar();
    }
    

数学相关函数

math.h头文件定义了各种数学函数和一个宏。在这个库中所有可用的功能都带有-一个double类型的参数,且都返
回double类型的结果
举例说明:

  1. double exp(double x)
    返回e的x次幂的值。
  2. double log(double x)
    返回x的自然对数(基数为e的对数)
  3. double pow(double x, double y)
    返回x的y次幂。
  4. double sqrt(double x)
    返回x的平方根。
  5. double fabs(double x)
    返回x的绝对值。

代码实现

#include <stdio.h>
#include <math.h>

void main() {
	double res = fabs(-3);	// 绝对值
	double res2 = sqrt(2);	// 平方根
	double res3 = exp(0);	// e的几次幂
	double res4 = log(1);	// 返回x的自然对数
	double res5 = pow(4, 5);// x的y次幂,第一个参数为x
	printf("res=%.2f res2=%.2f res3=%.2f res4=%.2f res5=%.2f", res, res2, res3, res4, res5);
	getchar();
}

基本数据类型和字符串类型的转换

在程序开发中,我们经常需要将基本数据类型转成字符串类型(即char数组)。或者将字符串类型转成基本数
据类型。

sprintf 函数的用法

  1. sprintf和平时我们常用的printf函数的功能很相似。sprintf函数打印到字符串中,而printf函数打印输出到屏幕
    上。sprintf函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛
  2. 该函数包含在stdio.h的头文件中

基本数据类型转字符串类型

#include <stdio.h>

void main() {
	char str1[30];
	char str2[30];
	char str3[30];
	int a = 20;
	double b = 30.0;
	sprintf(str1, "%d", a);
	sprintf(str2, "%.2f", b);
	sprintf(str3, "%8.2f", b);	// %8.2f 含义是输出8位,小数点占用两位,如果不够8位,就会用空格在前面占位
	printf("str1=%s str2=%s str3=%s", str1, str2, str3);
	getchar();
}

image-20210830102041894

字符串类型转基本数据类型

语法:通过<stdlib.h>的函数调用atoi .atof即可

注意事项

  1. 在将 char数组类型转成基本数据类型时,要确保能够转成有效的数据,比如我们可以把"123",转成一一个
    整数,但是不能把"hello"转成一-个整数
  2. 如果格式不正确,会默认转成0或者0.0

代码实现

#include <stdio.h>
#include <stdlib.h>		// 引入需要的函数

void main() {
	char str1[20] = "23423";
	char str2[20] = "343.3";
	char str3[20] = "ab";
	char str4[20] = "1111";
	int a = atoi(str1);
	double b = atof(str2);
	char c = str3[0];		// 索引
	long d = atol(str4);
	printf("a=%d b=%.2f c=%c d=%d", a, b, c, d);
	getchar();
}

九、预处理命令

1 基本介绍

预处理是在编译之前执行的操作

  1. 使用库函数之前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。
  2. 这些在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)
  3. 预处理 主要是处理以#开头的命令,例如#include <stdio.h> 等。预处理命令要放在所有函数之外,而且-般都放
    在源文件的前面
  4. 预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程
    序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译
  5. C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、
    修改、移植和调试,也有利于模块化程序设计

2 快速入门

具体要求

开发-一个C语言程序,让它暂停5秒以后再输出内容"春暖花开,世界和平",并且要求跨平台,在Windows和
Linux、下 都能运行,如何处理

提示

  1. Windows 平台下的暂停函数的原型是void Sleep(DWORD dwMilliseconds), 参数的单位是“毫秒”,位
    于<windows.h>头文件。
  2. Linux 平台下暂停函数的原型是unsigned int sleep (unsigned int seconds),参数的单位是“秒”,位于<unistd.h>
    头文件
  3. #if、 #elif、 #endif 就是预处理命令,它们都是在编译之前由预处理程序来执行的。

代码实现

#include <stdio.h>
#if _WIN32	// 如果是windows平台,执行#include <Windows.h>
#include <Windows.h>
#elif _linux_	// 如果是linux平台,#include <unistd.h>
#include <unistd.h>
#endif

void main() {
	// 不同的平台下调用不同的函数
	#if _WIN32
	Sleep(5000);	// 毫秒
	#elif _linux_
	Sleep(5);		// 秒
	#endif
	puts("春暖花开,世界和平");	// puts和printf一样的功能
	getchar();
}

3 C语言宏定义

  1. #define 叫做宏定义命令,它也是C语言预处理命令的- -种。所谓宏定义,就是用一个标识符来表示一个字符
    串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串
  1. 宏定 义我们在讲解常量时,做过介绍,这里我们再系统的讲解一下.

#defineN 100就是宏定义,N为宏名,100 是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现
的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为**“宏替换”或“宏展开”**。
宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的

4 宏定义的形式

  1. #表示这是一条预处理命令,所有的预处理命令都以#开头。宏名是标识符的一种,命名规则和变量相同。字.
    符串可以是数字、表达式、if语句、函数等
  2. 这里所说的字符串是- -般意义.上的字符序列,不要和C语言中的字符串等同,它不需要双引号
  3. 程序中反复使用的表达式就可以使用宏定义

代码演示

#include <stdio.h>

#define M (n*n+3*n)	// 只是简单的替换,如果不带括号又是另一种结果了
void main() {
	int sum, n;
	printf("请输入一个数字");
	scanf("%d", &n);
	sum = 3 * M + 4 * M + 5 * M;	// 宏展开	3*(n*n+3*n) + 4*(n*n+3*n) + 5*(n*n+3*n) 
	printf("sum=%d\n", sum);
	getchar();
	getchar();
}

宏定义注意事项和细节

  1. 宏定义是用宏名来表示-一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的替换。字符串中可
    以含任何字符,它可以是常数、表达式、if语句、函数等,预处理程序对它不作任何检查,如有错误,只能在
    编译已被宏展开后的源程序时发现。

  2. 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换

  3. 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令[案
    例]

#define PI 3.14159
int main(){
printf("PI=%f", PI); .
return 0;
}
#undefPI //取消宏定义
void func({
// Code
printf("PI=%f", PD);//错误,这里不能使用到PI了
  1. 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替[案例]
#include <stdio.h>
#define OK 100
int main(){
	printf("OK\n");	// 这里就相当于是字符串输出,不能进行替换
	return 0;
}
  1. 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换[ 案例]
#define PI 3.1415926
#define S PI*y*y
/* PI是已定义的宏名*/
print("%f", S);
// 在宏替换后变为:
print("%f", 3.1415926*y*y);
  1. 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母

  2. 可用宏定义表示数据类型,使书写方便[案例]

    #define UINT unsigned int
    void main() {
    	UINT a,b;	//宏替换 unsigned int a, b;
    }
    
  3. 宏定义表示数据类型和用typedef定义数据说明符的区别:宏定义只是简单的字符串替换,由预处理器来处理;
    typedef是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一-个新的名字,
    将它作为–种新的数据类型。

5 带参数的宏定义

  1. C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和
    函数有些类似
  2. 对带 参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参
  3. 带参宏定义的一般形式为**#define 宏名(形参列表)字符串**,在字符串中可以含有各个形参
  4. 带参宏调用的一般形式为:宏名(实参列表); [案例+说明]

代码演示

#include <stdio.h>

// 1 MAX就是带参数的宏
// 2 (a, b)就是形参
// 3 (a>b)?a: b是带参数的宏对应字符串,该字符串中可以使用形参.
#define MAX(a, b) (a > b) ? a : b
void main() { 
	int x, y, max;
	printf("input two numbers\n");
	scanf("%d%d", &x, &y);
	max = MAX(x, y);	// 宏替换	max = (x > y) ? x : y
	printf("max=%d", max);
	getchar();
	getchar();
}

带参宏定义的注意事项和细节

  1. 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现

    #define MAX(a,b) (a>b)?a:b
    如果写成了#define MAX (a, b) (a>b)?a:b
    将被认为是无参宏定义,宏名MAX代表字符串(a,b) (a>b)?a:b
    而不是: MAX(a,b)代表(a>b)?a:b 了
    
  2. 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,
    要用它们去替换形参,因此实参必须要指明数据类型

  3. 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。

    #include <stdlib.h>
    #define SQ(y) (y)*(y)//带参宏定义,字符串内的形参通常要用括号括起来以避免出错
    int main(){
        int a, sq;
        printf("input a number: "); .
        scanf("%d", &a);
        // 如果没有括号就会变成:a + 1 * a + 1 = a + a + 1	就不是我们想要的结果了
        sq= SQ(a+1);//宏替换(a+1)* (a+1)
        printf("sq=%d\n", sq);
        system("pause");
        return 0;
    }
    

6 带参宏定义和函数的差别

  1. 宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也
    不会占用内存。
  2. 函数是一 段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码.
  3. 案例说明 :要求使用函数计算平方值,使用宏计算平方值, 并总结二者的区别
#include <stdio.h>
#include <stdlib.h>

//int SQ(int y) {
//	return y * y;
//}

#define SQ(y) (y)*(y)
void main() {
	int i = 1;
	while (i <= 5) {
		//printf("%d^2=%d\n", i - 1, SQ(i++));
		printf("%d^2=%d\n", i - 2, SQ(i++));	// 宏展开 (i++) * (i++)
		// 宏定义它是直接替换,因此在这里它++了两次,所以i的变化时1 , 3 ,5
		// 函数它的i的变换是1,2,3,4,5 所以这就是两者的区别,要注意一下
	}
	system("pause");
}

7 预处理命令总结

预处理指令是以#号开头的代码行,#号必须是该行除了任何空白字符外的第-一个字符。#后是指令关键字,
在关键字和#号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编
译之前对源代码做某些转换

image-20210830115920840

预处理指令使用注意事项

  1. 预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的,程序员在程序中用预处理
    命令来调用这些功能。
  2. 宏定义可以带有参数,宏调用时是以实参代换形参,而不是“值传送”。
  3. 为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。
  4. 文件包含是预处理的-一个重要功能,它可用来把多个源文件连接成一- 个源文件进行编译,结果将生成-一个 目标
    文件。
  5. 条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程
    序的效率。
  6. 使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计

十、数组

数组可以存放多个同–类型数据。数组也是一种数据类型,是构造类型。传递是以引用的方式传递(即传递的
是地址)

1 快速入门

#include <stdio.h>

void main() {
	/*一个养鸡场有6只鸡,它们的体重分别是3kg, 5kg, 1kg,
		3.4kg, 2kg, 50kg 。请问这六只鸡的总体重是多少? 
		平均体重是多少 ? 请你编一一个程序。
	*/

	double arr[6];
	double total = 0.0;
	double avg = 0.0;
	int i, length;
	
	arr[0] = 3;
	arr[1] = 5;
	arr[2] = 1;
	arr[3] = 3.4;
	arr[4] = 2;
	arr[5] = 50;

	// 数组长度 = 数据的总字节 / 每个数据的字节
	length = sizeof(arr) / sizeof(double);
	for (i = 0; i < length; i++)
	{
		total += arr[i];
	}
	avg = total / length;
	printf("total=%.2f avg=%.2f", total, avg);
	getchar();
}

2 数据的定义和内存布局

①数据的定义

数据类型 数组名[数组大小];
inta[5]; //a 数组名,类型int,[5] 大小,即a数组最多存放5个int数据
赋初值a[0]= 1;a[1]= 30; …

②数据的内存分布

image-20210903102324822

说明:1.数组名就代表该数组的首地址,既a[0]的地址

? 2.数组各个元素是连续分布的

? 例:int类型的数组 -> a[0] 的地址为0x1233 ,那么a[1]的就是0x1233 + int的字节数 = 0x1237, a[2]的地址0x123B,后面以此类推。。。;是什么类型的数组后面就加对应的字节数

小案例

#include <stdio.h>

void main() {

	double arr[5];
	int i, length;
	length = sizeof(arr) / sizeof(double);
	for (i = 0; i < length; i++)
	{
		printf("\n 请输入你的分数");
		scanf("%lf", &arr[i]);	// 是l不是1
	}

	for (i = 0; i < length; i++)
	{
		// 输出成绩
		printf("%d=%.2f", i, arr[i]);
	}

	getchar();	// 过滤回车
	getchar();
}

③三种初始化数组的方式

// 第一种
int arr[2];
arr[0] = 200;
// 第二种
int arr2[3] = { 1, 2, 3 };
// 第三种
int arr3[] = { 5, 4, 6 };

3 数据使用注意事项和细节

  1. 数组是多个相同类型数据的组合,一个数组一-旦声明/定义了,其长度是固定的,不能动态变化
  2. 数组创建后, 如果没有赋值,则遵守如下规则
    全局数组默认值0
    非全局数组初值是机器垃圾值(即:原来系统分配给这块空间的值)
  3. 使用 数组的步骤1. 定义数组2给数组各个元素赋值3使用数组,也可以一一步到位
  4. 数组的 下标是从0开始的,不是从1开始。
  5. 数组下标必须在指定范围内使用,编译通过,在运行时会因为数组越界而异常中断:
    比如intarr[5] 有效下标为0-4
  6. C的数组属构造类型,是引用传递(传递的是地址),因此当把一个数组传递给–个函数时/或者变量,函数/变
    量操作数组会影响到原数组(因为传参的时候传过去的是指针,也就是地址)

4 数组应用案例

1)创建一个 char类型的26个元素的数组,分别放置’A’-Z‘。使用for循环访问所有元素并打印出来。提示:字符数据运算’A’+1 -> ‘B’

#include <stdio.h>

void main() {
	/*
		创建一个 char类型的26个元素的数组,分别放置'A' - Z‘。使用for循环访问所有元素并打印出来。
	    提示:字符数据运算'A' + 1 -> 'B'
	*/
	char arr[26];
	int i;
	for (i = 0; i < 26; i++)
	{
		arr[i] = 'A' + i;
	}
	for ( i = 0; i < 26; i++)
	{
		printf("%c ", arr[i]);
	}
	getchar();
}

2)请求出一个数组的最大值,并得到对应的下标。

#include <stdio.h>

void main() {
	// 请求出一个数组的最大值,并得到对应的下标。
	int arr[] = {2, 3, 456, 554, 34, 5464};
	int i, length, maxIndex;
	double max = arr[0];
	length = sizeof(arr) / sizeof(int);
	for (i = 1; i < length; i++)
	{
		if (arr[i] > max) {
			max = arr[i];
			maxIndex = i;
		}
	}
	printf("最大值为%.2f, 对应的下角标为%d", max, maxIndex);
	getchar();
}

5 字符数组与字符串

字符数组实际上是一系列字符的集合,也就是字符串(String) 。在C语言中,没有专门的字符串变量,没有
string类型,通常就用一个字符数组来存放-一个字符串

#include <stdio.h>

void main() {

	char str[] = "莫个超是靓仔";
	printf("str=%s", str);
	getchar();
}

本 次

1 字符数组注意事项

  1. 在C语言中,字符串实际上是使用null 字符(\0’) 终止的一维字符数组。因此,一个以null 结尾的字符串,
    包含了组成字符串的字符。
  2. ^\0’是 ASCII码表中的第0个字符,用NUL表示,称为空字符。该字符既不能显示,也不是控制字符,输出该
    字符不会有任何效果,它在C语言中仅作为字符串的结束标志。
  3. 字符数组(字符 串)在内存中的布局分析[案例]

image-20210903115355323

说明:"?"表示是不知道的东西,可能是垃圾值,也可能是其他的东西

结论如果在给某个字符数组赋值时,(1 )赋给的元素的个数小于该数组的长度,则会自动在后面加\0’, 表示
字符串结束,(2)赋给的元素的个数等于该数组的长度,则不会自动添加\O’
char str2[]= {‘t,m’,‘o’} 输出什么?输出的是tmo 乱码.

image-20210903191323933

补充

char str2[] = { 't', 'o', 'm' };	// 这个也是不会自动添加'/0'的

2 字符串的访问和遍历

因为字符串的本质就是字符数组,因此可以按照数组的方式遍历和访问某个元素

代码演示

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main() {
	char str[] = "hello";
	int i;
	int length = strlen(str);
	printf("str=%s", str);
	printf("\n str的长度是%d", length);
	printf("\n 字符串第三个字是%c", str[2]);
	// 遍历字符数组
	for ( i = 0; i < length; i++)
	{
		printf("\n %c", str[i]);
	}
	system("pause");
}

6 字符串的表现形式

① 用字符数组存放字符串

char str[] = "我是靓仔智";
char str2[] = {'h','e','l','l','o'}

② 用字符指针指向一个字符串

  1. C语言对字符串常量"hellotom"是按字符数组处理的,在内存中开辟了一一个字符数组用来存放字符串常量,程
    序在定义字符串指针变量str时==只是把字符串首地址(即存放字符串的字符数组的首地址)赋给pStr==.

  2. printf("%s\n",str); 可以输出str 指向的字符串

  3. 对应的内存布局图(!!)

    image-20210903194802994

③ 两种方法表示字符串的讨论

  1. 字符数组由若千个元素组成,每个元素放一一个字符;而字符指针变量中存放的是地址(字符串/字符数组的首地址),绝不是将字符串放到字符指针变量中(是字符串首地址)[图]

  2. 对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值

    char str[5];
    str = "hello";	// 错误
    str[2] = 'i';	// 正确
    system("pause");
    

    image-20210903200418514

  3. 对字符指针变量,采用下面方法赋值,是可以的

    char* str = "我是靓仔智";
    str = "世界和平,春暖花开";		// 指向一个新开的空间呗
    
  4. 如果定义了一个字符数组,那么它有确定的内存地址(即字符数组名是一个常量);而定义一个字符指针变量时,
    它并未指向某个确定的字符数据,并且可以多次赋值[代码+图解]

7 字符串相关函数

① 常用函数

image-20210903200756352

② 字符串函数应用案例

#include <stdio.h>
#include <string.h>

void main() {
	char str[] = "hello";
	char str2[] = "world";
	char str3[] = "";
	strcpy(str3, str2);		// s2复制到s1
	printf("复制的是%s\n", str);
	strcat(str, str2);	// 追加
	printf("append为%s\n", str);
	int result = strcmp(str, str2);	// 判断两个字符串是否相同,相同返回0
	printf("result=%d\n", result);
	char* p = strchr(str, 'e');		// 要用指针类型接收,没有找到返回null
	printf("p=%s\n", p);
	char* p2 = strstr(str, "ll");	// 要用指针类型接收,没有找到返回null
	printf("p2=%s\n", p2);
	getchar();
}

8 字符串使用注意项和细节

  1. 程序中往往依靠检测\0’ 的位置来判定字符串是否结束,而不是根据数组的长度来决定字符串长度。因此,字
    符串长度不会统计"\0’, 字符数组长度会统计[案例]

    #include <stdio.h>
    #include <string.h>
    
    void main() {
    	char str[] = "hello";
    	int len = strlen(str);		// 这个输出的就是5
    	int arrLen = sizeof(str);	// 数组输出就是6,因为还有一个'\0'
    	printf("%d  %d", len, arrLen);	// 5	6
    	getchar();
    }
    
  2. 在定 义字符数组时应估计实际字符串长度,保证数组长度始终大于字符串实际长度,否则, 在输出字符数组
    时可能出现未知字符.

    image-20210904095856417

  3. 系 统对字符串常量也自动加一个\0’作为结束符。例如"C Program”共有9个字符,但在内存中占10个字节,
    最后一个字节\0’是系统自动加上的。( 通过sizeof()函数可验证)

  4. 定 义字符数组时,如果给的字符个数比数组的长度小,则系统会默认将剩余的元素空间,全部设置为’\0’, 比
    如char str[6] = “ab” , str内存布局就是 [a] [b] [\0] [\0] [\0] [\0]

字符数组练习

image-20210904100659937

9 多维数组 - 二维数组

数组里面放了个数组(套娃)

语法:类型 数组名[大小] [大小]

①快速入门

image-20210905102026679

代码演示

#include <stdio.h>

void main() {
	int i, j;
	int arr[4][6];

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 6; j++)
		{
			arr[i][j] = 0;
		}
	}

	arr[1][2] = 1;
	arr[2][1] = 2;
	arr[2][3] = 3;

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 6; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf("\n");
	}

	getchar();
}

②二维数组内存分布

#include <stdio.h>

void main() {
	int i, j;
	int arr[4][6];

	for (i = 0; i < 4; i++)
	{
		for (j = 0; j < 6; j++)
		{
			arr[i][j] = 0;
		}
	}
	printf("二维数组arr的首地址=%p\n", arr);
	printf("二维数组arr[0]的地址=%p\n", arr[0]);
	printf("二维数组arr[0][0]的地址=%p\n", &arr[0][0]);
	printf("二维数组arr[0][1]的地址=%p\n", &arr[0][1]);
	getchar();
}

说明:arr、arr[0]和&arr[0] [0]的地址是一样的,都是首地址

内存分析图

image-20210905104849477

注意:他们并不是一行行分开的,而是连续的地址

③直接初始化数组

  1. 定义 类型 数组名[大小] [大小] = {{值1, 值2, 值3},{值1, 值2, 值3},{值1, 值2, 值3}};
  2. 或者 类型 数组名[大小] [大小] = 值1,值2,值3,值4,值5,值…};

image-20210905104938760

④二维数组练习

#include <stdio.h>

/*	
	定义二维数组,用于保存三个班,每个班三名同学成绩,
	并求出每个班级平均分、以及所有班级平均分
*/

void main() {
	int i, j;
	// 定义一个二维数组
	double score[3][3];
	// 行数 = 总字节数 / 每一行的字节数
	int rows = sizeof(score) / sizeof(score[0]);
	int cols = sizeof(score[0]) / sizeof(double);
	double totalScore = 0.0;	// 所有学生总成绩
	double avgScore = 0.0;		// 各班平均分

	// 初始化
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			score[i][j] = 0;	
		}
	}
	// 从键盘输入成绩
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			printf("请输入%d班第%d个学生的成绩:", i + 1, j + 1);
			scanf("%lf", &score[i][j]);
		}
	}
	printf("\n\n");
	// 打印所有学生的成绩
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			printf("%.2f ", score[i][j]);
		}
		printf("\n");
	}
	printf("\n\n");
	// 计算所有学生总分和各班平均分
	for (i = 0; i < rows; i++)
	{
		for (j = 0; j < cols; j++)
		{
			totalScore += score[i][j];
			avgScore += score[i][j];
		}
		printf("%d班的平均分为:%.2f\n", i + 1, avgScore / 3);
		avgScore = 0.0;
	}
	printf("\n\n所有学生的总分:%.2f", totalScore);
	getchar();
	getchar();
}

⑤二维数组使用细节和注意事项

  1. 可以只对部分元素赋值,未赋值的元素自动取“零”值[案例]

    int main({
        int a[4][5]= {{1}, {2}, {3},{}};
        int ij;
        for(i=0;i<4;i++) {
        for(j=0;j<5;j++){
        printf("%d ",a[i][j]);
        }
        print("\n"); .
        getchar(;
    }
    
  2. 如果对全部元素赋值,那么第一维的长度可以不给出。比如:

    int a[3][3]= {1,2,3,4,5,6, 7,8, 9};
    可以写为:
    inta[][3]= {1,2,3,4,5,6, 7,8, 9};
    
  3. 二维数组可以看作是由一维数组嵌套而成的;如果- - 个数组的每个元素又是-一个数组,那么它就是二维数组。

    二维数组a[3][4]可看成三个- -维数组,它们的数组名分别为a[0]、 a[1]、 a[2]。
    这三个-维数组都有4个元素,如,一维数组 a[0] 的元素为a[0][0]、 a[0][1]、 a[0][2]、 a[0][3]
    

十一、排序和查找

1 冒泡排序

image-20210904110128294

代码实现

#include <stdio.h>

// 冒泡排序函数
void bubbleSore(int arr[], int len) {
	int i, j, t;
	for (i = 0; i < len - 1; i++)
	{
		for (j = 0; j < len - 1 - i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
			}
		}
	}
}

void main() {
	int arr[] = {65, 43, 75, 23, 100};
	
	int i, len;
	len = sizeof(arr) / sizeof(int);
	bubbleSore(arr, len);
	printf("排序后\n");
	for (i = 0; i < len; i++)
	{
		printf("%d\n", arr[i]);
	}
	getchar();
}

2 查找

①顺序查找

逐个比较

#include <stdio.h>

int searchSeq(int arr[], int length, int index) {
	int i;
	for (i = 0; i < length; i++)
	{
		if (arr[i] == index)
		{
			return arr[i];
		}
	}

}

void main() {
	int length;
	int arr[] = {2, 4, -2, 22, 90};
	length = sizeof(arr) / sizeof(int);
	int index = searchSeq(arr, length, 90);
	printf("index=%d", index);
	getchar();
}

②二分查找

前提:必须是有序数组

说明:从中间开始查找,大于中间的数就往右边查找,小于就往左边查

代码实现

#include <stdio.h>

// 二分法查找
int binarySearch(int arr[], int leftIndex, int rightIndex, int findVal) {
	int i;
	int midIndex = (leftIndex + rightIndex) / 2;	// 中间值的索引
	int midVal = arr[midIndex];						// 中间值
	if (midVal > findVal)	// 中间值大于要找的值,往左边找
	{
		for (i = midIndex - 1; i > leftIndex; i--)
		{
			if (arr[i] == findVal)
			{
				return i;
			}
		}
	}
	else if (midIndex < findVal) {	// 中间值小于要找的值,往右边找
		for (i = midIndex + 1; i < rightIndex; i++)
		{
			if (arr[i] == findVal)
			{
				return i;
			}
		}
	}
	else
	{
		return 0;	// 返回该数的下标
	}
}

void main() {
	int arr[] = { 2, 30, 34, 45, 90 };
	int arrLen = sizeof(arr) / sizeof(int);
	int index = binarySearch(arr, 0, arrLen, 90);
	if (index != 1) {
		printf("找到了,它的索引值是:%d", index);
	}
	else
	{
		printf("没有找到");
	}
	getchar();
}

十二、断点调试

image-20210905111057936

十三、指针

1 指针的基本介绍

和java中的引用数据类型类似,指针就是一个可以操作对应地址的变量的东西

  1. 指针是C语言的精华,也是C语言的难点。
  2. 指针, 也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。关于指针的基本使用,在讲变量的
    时候做了入门级的介绍
  3. 获取 变量的地址,用&,比如:
    int num= 10,获取num的地址: &num
  4. 指针类型, 指针变量存的是一个地址,这个地址指向的空间存的才是值
    比如: intptr = # ptr 就是指向int 类型的指针变量,即ptr是int 类型。
  5. 获取指针类 型所指向的值,使用: (取值符号), 比如: int ptr,使用*ptr 获取ptr指向的值

什么是指针

指针是一一个变量,其值为另一个变量的地址(前示意图已经说明),即,内存位置的直接地址。就像其他变量或
常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

int *ip;	/*一个整型的指针*/
double *dp;	/*一个double 型的指针*/
float *fp; 	/*一个浮点型的指针*/
char *ch;  	/*一个字符型的指针*/

image-20210816205701239

2 指针的算数运算

指针是一个用数值表示的地址。可以对指针执行算术运算。可以对指针进行四种算术运算: ++、-、+. -。

就是对指针中的地址进行算数运算,以单位来计算的,是什么类型就是什么单位,比如int类型的指针就是一个单位4个字节

①指针递增操作(++)

代码演示

#include <stdio.h>

void main() {
	int i, length, *ptr;
	int val[] = {23, 53, 45};
	length = sizeof(val) / sizeof(int);
	ptr = val;
	for (i = 0; i < length; i++)
	{
		printf("val[%d] 地址=%p \n", i, ptr);
		printf("val[%d]=%d \n", i, *ptr);
		ptr++;	// ptr = ptr的地址值 + 一个单位(1个int类型的字节数)
	}
 	getchar();
}

内存示意图

image-20210905185552048

②指针递减操作(–)

原理和++一样

#include <stdio.h>

void main() {
	int i, *ptr, length;
	int var[] = {23, 34, 54};
	length = sizeof(var) / sizeof(int);
	ptr = &var[length - 1];	// 指针指向最后一个值
	for ( i = length - 1; i >= 0; i--)
	{
		printf("val[%d] 地址=%p \n", i, ptr);
		printf("val[%d]=%d \n", i, *ptr);
		ptr--;	// ptr = ptr的地址值 - 一个单位(1个int类型的字节数)
	}
	getchar();
}

注意:指针接收的是地址,不是值

③指针+、-操作

和普通的类型,不过指针是以单位来计算的,单位也就是对应指针类型的字节数

#include <stdio.h>

void main() {
	int arr[] = {10, 30, 230};
	int i, * ptr;
	ptr = arr;
	ptr += 2;	// ptr的地址 + 2个int字节(8个字节)
	printf("arr[2]=%d var[2]的地址=%p\nptr存储的地址=%p ptr指向的值=%d", arr[2], &arr[2], ptr, *ptr);
	getchar();
}

④练习

#include <stdio.h>

// 指向第三个元素
void main() {
	int i, * ptr;
	int var[] = { 20, 30, 50 };
	ptr = &var[2];
	printf("ptr指向的值为%d, 地址为%p a[2]的地址为%p", *ptr, ptr, &var[2]);
	getchar();
}

3 指针的比较

void main() {
int length;
int arr[] = {2, 4, -2, 22, 90};
length = sizeof(arr) / sizeof(int);
int index = searchSeq(arr, length, 90);
printf(“index=%d”, index);
getchar();
}




### ②二分查找

**前提**:必须是有序数组

说明:从中间开始查找,大于中间的数就往右边查找,小于就往左边查



**代码实现**

```c
#include <stdio.h>

// 二分法查找
int binarySearch(int arr[], int leftIndex, int rightIndex, int findVal) {
	int i;
	int midIndex = (leftIndex + rightIndex) / 2;	// 中间值的索引
	int midVal = arr[midIndex];						// 中间值
	if (midVal > findVal)	// 中间值大于要找的值,往左边找
	{
		for (i = midIndex - 1; i > leftIndex; i--)
		{
			if (arr[i] == findVal)
			{
				return i;
			}
		}
	}
	else if (midIndex < findVal) {	// 中间值小于要找的值,往右边找
		for (i = midIndex + 1; i < rightIndex; i++)
		{
			if (arr[i] == findVal)
			{
				return i;
			}
		}
	}
	else
	{
		return 0;	// 返回该数的下标
	}
}

void main() {
	int arr[] = { 2, 30, 34, 45, 90 };
	int arrLen = sizeof(arr) / sizeof(int);
	int index = binarySearch(arr, 0, arrLen, 90);
	if (index != 1) {
		printf("找到了,它的索引值是:%d", index);
	}
	else
	{
		printf("没有找到");
	}
	getchar();
}

十二、断点调试

[外链图片转存中…(img-EyvPfxk0-1630841155427)]

十三、指针

1 指针的基本介绍

和java中的引用数据类型类似,指针就是一个可以操作对应地址的变量的东西

  1. 指针是C语言的精华,也是C语言的难点。
  2. 指针, 也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。关于指针的基本使用,在讲变量的
    时候做了入门级的介绍
  3. 获取 变量的地址,用&,比如:
    int num= 10,获取num的地址: &num
  4. 指针类型, 指针变量存的是一个地址,这个地址指向的空间存的才是值
    比如: intptr = # ptr 就是指向int 类型的指针变量,即ptr是int 类型。
  5. 获取指针类 型所指向的值,使用: (取值符号), 比如: int ptr,使用*ptr 获取ptr指向的值

什么是指针

指针是一一个变量,其值为另一个变量的地址(前示意图已经说明),即,内存位置的直接地址。就像其他变量或
常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

int *ip;	/*一个整型的指针*/
double *dp;	/*一个double 型的指针*/
float *fp; 	/*一个浮点型的指针*/
char *ch;  	/*一个字符型的指针*/

[外链图片转存中…(img-nBA1rrDC-1630841155428)]

2 指针的算数运算

指针是一个用数值表示的地址。可以对指针执行算术运算。可以对指针进行四种算术运算: ++、-、+. -。

就是对指针中的地址进行算数运算,以单位来计算的,是什么类型就是什么单位,比如int类型的指针就是一个单位4个字节

①指针递增操作(++)

代码演示

#include <stdio.h>

void main() {
	int i, length, *ptr;
	int val[] = {23, 53, 45};
	length = sizeof(val) / sizeof(int);
	ptr = val;
	for (i = 0; i < length; i++)
	{
		printf("val[%d] 地址=%p \n", i, ptr);
		printf("val[%d]=%d \n", i, *ptr);
		ptr++;	// ptr = ptr的地址值 + 一个单位(1个int类型的字节数)
	}
 	getchar();
}

内存示意图

[外链图片转存中…(img-N0ofAckF-1630841155429)]

②指针递减操作(–)

原理和++一样

#include <stdio.h>

void main() {
	int i, *ptr, length;
	int var[] = {23, 34, 54};
	length = sizeof(var) / sizeof(int);
	ptr = &var[length - 1];	// 指针指向最后一个值
	for ( i = length - 1; i >= 0; i--)
	{
		printf("val[%d] 地址=%p \n", i, ptr);
		printf("val[%d]=%d \n", i, *ptr);
		ptr--;	// ptr = ptr的地址值 - 一个单位(1个int类型的字节数)
	}
	getchar();
}

注意:指针接收的是地址,不是值

③指针+、-操作

和普通的类型,不过指针是以单位来计算的,单位也就是对应指针类型的字节数

#include <stdio.h>

void main() {
	int arr[] = {10, 30, 230};
	int i, * ptr;
	ptr = arr;
	ptr += 2;	// ptr的地址 + 2个int字节(8个字节)
	printf("arr[2]=%d var[2]的地址=%p\nptr存储的地址=%p ptr指向的值=%d", arr[2], &arr[2], ptr, *ptr);
	getchar();
}

④练习

#include <stdio.h>

// 指向第三个元素
void main() {
	int i, * ptr;
	int var[] = { 20, 30, 50 };
	ptr = &var[2];
	printf("ptr指向的值为%d, 地址为%p a[2]的地址为%p", *ptr, ptr, &var[2]);
	getchar();
}

3 指针的比较

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-06 10:59:24  更:2021-09-06 11:01:59 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 -2024/12/27 20:27:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码
数据统计