Linux C语言基础学习总结

一、Linux系统的基本使用

1.1 打开终端的方式

1.点击左侧图标

2.快捷键ctrl+alt+t

3.同窗口打开多个终端ctrl+shift+t

4.同路径打开新终端ctrl+shift+n

1.2 命令行提示符

例如:linux@ubuntu:~$

linux        当前用户名

          分隔符

ubuntu     主机名

            分隔符

            用户家目录

            用户权限为普通用户

1.3 linux文件系统结构

 linux的文件系统是一个树形结构

绝对路径:相对于根目录的路径

相对路径:相对于当前所在路径的路径

1.4 linux常用命令

格式:命令      选项      参数

1.4.1 ls命令

ls        列出当前路径下的所有文件

ls 路径名        列出此路径下的所有文件

ls  -l        列出当前路径下的所有文件,包括文件的详细信息

ls -a         列出当前路径下的所有文件,包括隐藏文件

ls -lh         列出当前路径下的所有文件,包括文件的详细信息,且文件的大小以 K M G 等形式显示

1.4.2 cd命令

cd  目录名         进入指定的目录(可以是绝对路径 也可以是相对路径)

cd .         进入当前路径 --相当于没动

cd ..         进入上一级目录

cd ~         进入用户的家目录 也可以直接 cd 回车

cd /         进入根目录

cd -         进入上一次所在的目录

cd ../../         进入上一级的上一级目录 (可以连续使用)

1.4.3 mkdir 命令

mkdir dir1          在当前路径下创建一个目录文件 名为dir1

mkdir /home/linux/dir3         在/home/linux 创建一个目录文件 名为 dir3

mkdir dir1 dir2 dir3         在当前路径下 并列的创建三个目录文件 分别名为 dir1 dir2 dir3

mkdir -p dir1/dir2/dir3         在当前路径下 嵌套创建三个目录文件

1.4.4 rm 命令

rm file         删除当前路径下名为file的文件

rm -r dir         删除当前路径下名为dir的目录文件

rm -f file         强制删除文件file 忽略错误信息

rm -rf 文件名         常用的用法

1.4.5 cp命令

cp file1 file2         如果file2不存在 将文件file1复制一份儿取名为file2

                            如果file2存在,表示将file1复制一份儿取名为file2 会覆盖原来的file2

cp file1 dir1          将文件file1复制一份儿放到目录dir1里面

cp -r dir1 dir2       如果目录dir2不存在,表示将目录dir1复制一份儿取名为dir2

                            如果目录dir2存在,表示将目录dir1复制一份儿放到dir2里面

1.4.6 mv命令

mv file1 file2         如果file2不存在,表示将文件file1重命名为file2

                             如果file2存在,表示将文件file1重命名为file2,覆盖原来的file2

mv file1 dir1         将file1移动到dir1中

mv dir1 dir2          如果dir2不存在,表示将dir1重命名为dir2

                             如果dir2存在,表示将dir1移动到dir2里面

1.4.7 其他常用命令

whoami        显示当前使用的用户

pwd          显示当前所在的路径

rmdir 目录文件名        删除目录文件(只能是空目录)

touch file        如果文件file存在,更新文件的时间戳

                      如果文件file不存在,表示在当前路径新建普通文件名为file

cat  文件名          查看文件的内容

exit 命令              表示退回到上一次的用户,如果本来就已经是最初的用户了表示关闭终端

su 用户名            切换用户

1.5 vi编辑器

1.5.1 命令行模式

打开一个文件,默认的就是在命令行模式,命令行一般是用来复制粘贴等操作的,其他模式回到命令行模式 按 Esc 键。

常用命令:

yy         复制光标所在行

nyy       复制从光标所在行开始的n行 n是一个数字

p         在光标所在行下面开始粘贴

P         在光标所在行上面开始粘贴

dd         剪切光标所在行

ndd        剪切从光标所在行开始的n行 n是一个数字

gg         将光标定位到第一行

G         将光标定位到最后一行

ngg       将光标定位到第n行 n是一个数字

/find      在全文件中查找单词find 回车之后 n 查找下一个 N 查找上一个

u          撤销

ctrl + r    反撤销 也就是重做

shift zz    保存退出 等价于 :wq

1.5.2 插入模式

是用来编辑文本的,也就是写代码的,在命令行模式下,按:

i         在光标所在位置前面开始插入

I         在光标所在行的行首开始插入

a         在光标所在位置后面开始插入

A         在光标所在行的行尾开始插入

o         在光标所在行下面新起一行开始插入

O         在光标所在行上面新起一行开始插入

1.5.3 底行模式

在命令行模式下按 空格或冒号 : 进入底行模式

底行模式常用命令:

:w         保存

:q         退出

:wq         保存并退出 使用 :x 也可以

:noh         取消查找的高亮显示

:set nu         显示行号 :set number 也可以

:set nonu         取消显示行号 :set nonumber 也可以

:%s/aa/bb/g         将全文的aa都替换成bb

:%s/aa/bb/gc         将全文的aa都替换成bb,会询问每一个是否替换 y 替换 n 不替换

:vsp 文件名         左右分屏打开多个文件进行编辑 ctrl ww 切换编辑窗口

:wqa         将所有打开的文件都保存退出

二、hello world 程序说明

#include <stdio.h>

// C语言中以#开头的行 称为预处理行 是在预处理阶段处理的
// #include 是包含头文件的关键字
// stdio.h 标准输入输出的头文件 我们的使用的printf就在这个头文件里

//int 函数的返回值类型 先不用管
//main 主函数 是程序的入口 每个程序必须有且仅有一个
//() 里面是main函数的参数 先不用管 ()里面的内容可以不写 但是 ()必须写
//可以写成  int main()
//	()圆括号  []方括号  {}花括号
int main(int argc, const char *argv[])
{
	//{}里面包住的部分叫做函数体,就是我们实际要执行的代码

	//printf 是系统给我们提供的输出的函数 
	//功能就是将 "" 内的内容打印到终端  \n 表示换行的意思 就是回车
	printf("hello world\n"); //C语言中每条指令结束 都需要加 分号
	return 0;//函数的返回值  先不用管
}

//单行注释

/*
	多行
	注释
*/

#if 0
	多行
	注释
#endif

gcc编译器的使用

gcc   xxx.c        这种编译编译方式会在当前路径下,默认生成一个叫做 a.out 的可执行文件

gcc xxx.c -o diy_name         文件名 diy_name 是自定义的可执行文件名,这种编译编译方式会在当前路径下,生成一个 自定义名字 的可执行文件

按照编译流程分步骤编译:预处理-->编译-->汇编-->链接

三、数据的存储

计算机中数据的存储分为两大部分

数值型数据         如:520 1314

非数值型数据        如: "www.baidu.com"

3.1 数值型数据的存储

1. 十进制

在C语言中,没有特殊说明和前导符时,默认的都是10进制数据        特点:逢10进1

十进制转二进制

使用除2取余法:用十进制数除以2,保留商和余数,然后再用商继续除以2,保留商和余数,依次类推,直到商为 0,将得到的余数倒序取出就是对应的二进制数了。

2. 二进制

前导符 0b      特点:逢2进1  

二进制转十进制:

从右到左算        如:0b1110 == 0*2^0 + 1*2^1 + 1*2^2 + 1*2^3 == 0 + 2 + 4 + 8 = 14

其他进制转换十进制都可以使用这种方式,只不过将底数的2换成对应进制的数字即可

也可以使用8421的方式转换

1000 --> 8

0100 --> 4

0010 --> 2

0001 --> 1

3. 八进制

前导符 0(数字零)        特点:逢8进1

八进制转二进制:

从右向左,1位八进制数表示 3位二进制数        如:0735 -->0b111011101

二进制转八进制同理        如:0b010110101 --> 0265

4. 十六进制

前导符 0x        特点:逢16进1        每一位上的数字范围0123456789abcdef(字母大小写均可)

例如:0x4F5D 0x1234 0xABCD

十六进制转二进制:

从右向左,1位十六进制数表示 4位二进制数        如:0x45EF -->0b0100010111101111

二进制转十六进制同理        如:0b001010110101 --> 0x2B5

注意:不管几进制的数据,在计算机中都会转换成二进制来处理。

3.2 非数值型数据的存储

计算机中只能处理二进制的数值型数据,而在编码的过程中,经常也会出现一些非数值型的数据如:网址、人名、企业名 等,所以就发明一种叫 ascii码 的东西,专门用来表示非数值型数据

常见的字符对应的ascii码

'\0' --> 0         '\n' --> 10

空格 SPACE --> 32         '0'~'9' --> 48~57

'A'~'Z' --> 65~90         'a'~'z' --> 97~122

转义字符:

C语言中还定义了一个 '\' + 字母 来表示一些无法显示的字符,这些字符就叫做转义字符,因为字母已经不是本身的含义了

注意 : 转义字符虽然有两个符号 (一个反斜杠 和 一个字母),但是转义字符也是一个字符。

3.3 词法符号

1. 关键字

所谓的关键字就是编译器已经规定好的一些单词,直接使用即可,不用提前声明或者定义。

2. 标识符

命名规范:

只能由数字、字母、或者下滑线组成,不能以数字开头,不能和关键字冲突,起的名字尽量做到望文知意

四、数据类型

C语言的本质是操作内存。

内存和硬盘的区别:内存:数据访问快,掉电丢失。硬盘:数据访问慢,掉电不丢失。

C语言内存分配的最小单位是 字节 B

单位的换算:

1Byte = 8bit        1KB = 1024B        1MB = 1024KB        1GB = 1024MB        1TB = 1024GB

4.1 数据类型的分类

基本类型:整数类型、实型(小数)、枚举类型

构造类型:数组、结构体、共用体

指针类型:*

空类型:void

4.1.1 整数类型

包括char(字符类型) short(短整型) int(整型) long(长整型) long long(长长整型)

每个类型又分为有符号(signed)的和无符号(unsigned)的,如果不写有无符号,默认的都是有符号的,对于有符号数,最高位为符号位,剩下的叫数据位

符号位为0 表示正数, 符号位为1 表示负数

1. char

占用内存空间的大小 1字节

存储数据的范围:无符号:0 ~ 2^8-1        有符号:-2^7 ~ 2^7-1

2. short

占用内存空间的大小 2字节

存储数据的范围        无符号:0 ~ 2^16-1        有符号:-2^15 ~ 2^15-1

3. int

占用内存空间的大小 4字节

存储数据的范围        无符号:0 ~ 2^32-1        有符号:-2^31 ~ 2^31-1

4. long        

也可以写成 long int ,在32位操作系统中 和 int 一样,在64位操作系统中 和 long long 一样

5. long long

也可以写成 long long int ,占用内存空间的大小 8字节

存储数据的范围        无符号:0 ~ 2^64-1        有符号:-2^63 ~ 2^63-1

4.1.2 实型数据的存储

实型数据就是小数,也叫浮点型。 如:3.14

float        单精度浮点型        4字节

double    双精度浮点型        8字节

浮点型存储涉及到小数的二进制,是一个拼凑的近似值。

4.2 原码、反码和补码

数据在存储的时候设计到原码、反码和补码相互转换的问题。

原码:是给人类看的

反码:用来做原码和补码转换的

补码:是给计算机看的

无符号数:原码、反码、补码都是一样的

有符号的正数:原码、反码、补码都是一样的

有符号的负数:

反码 = 原码的符号位不变,其他位按位取反 0变1 1变0

补码 = 反码+1

注意:以char为例,1000 0000 规定为 -128 的补码

4.3 常量

在整个程序运行的过程中,值不会发生变化的量。如:123  3.14  "hello" 

1. 整型常量:

前导符输出占位符例如
二进制0b0b1010
八进制0%#o0765
十进制%d1314
十六进制0x%x

0xAB12

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//常量一般是用来给变量赋值的
	short v1 = 520;
	printf("v1 = %d\n", v1);//520

	long int v2 = 1314;
	printf("v2 = %ld\n", v2);//1314

	return 0;
}

2. 浮点型常量(实型):

输出占位符例如
float%f3.14
double%lf5.28
#include <stdio.h>

int main(int argc, const char *argv[])
{
	float f1 = 3.1415926;
	printf("f1 = %f\n", f1);//  3.141593 默认显示6位小数 超过的部分 四舍五入
	//也可以使用 %.nf 的方式 来指定显示n位小数
	printf("f1 = %.2f\n", f1);// 3.14

	//如果是double 占位要用 %lf
	double f2 = 3.1415926;
	printf("f2 = %lf\n", f2);// 3.141593 默认也是显示6位小数
	printf("f2 = %.2lf\n", f2);// 3.14

	//指数形式
	float f3 = 314;
	printf("指数: f3 = %e\n", f3);// 3.140000e+02  可以按指数形式输出

	//也可以使用指数常量给变量赋值
	float f4 = -5.67e-3;
	printf("f4 = %f\n", f4);//-0.005670

	return 0;
}

3. 其他常量:

输出占位符例如
字符常量%c'M'
字符串常量%s'hello'
指数常量%e3.14e2

字符常量

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//定义一个字符类型的变量 用来保存 字符常量 'H'
	char v1 = 'A';
	printf("v1 = [%d] [%c]\n", v1, v1);// 65  A

	//字符常量也可以参与运算  --本质就是 ascii码的运算
	char v2 = 'M'+1;  //v2中实际存储的是 'N' 的ascii码
	printf("v2 = [%d] [%c]\n", v2, v2);// 78 N

	char v3 = 67;//使用字符的ascii码给变量赋值也可以
	printf("v3 = [%d] [%c]\n", v3, v3);// 67 C

	//上述例子中可以得到结论:
	//字符就是整形 整形就是字符 具体看我们怎么用
	
	return 0;
}

字符串常量

字符串常量需要用 双引号 "" 引起来,如:"www.baidu.com"

每个字符串常量结尾都会有一个隐藏的字符 '\0' 用来标识字符串结束的

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//可以将字符串直接输出
	printf("hello\n");
	printf("%s\n", "hello");
 
    //C语言中对字符串的处理 遇到 '\0' 就结束了
	char *p2 = "bei\0jing";
	printf("p2 = %s\n", p2);//bei

	return 0;
}

标识常量:    宏定义    #define

宏定义的名字要符合标识符的命名规范,一般都是大写的格式:

例如:

#include <stdio.h>

#define STUDENT 30  //当宏值发生变化时,所有使用宏名的地方都会变化

int main(int argc, const char *argv[])
{
	printf("当前班级已有人数 %d\n", STUDENT);
	printf("当前班级已有人数 %d\n", STUDENT);
	printf("当前班级已有人数 %d\n", STUDENT);

	return 0;
}

注意事项:

1.宏定义是在预处理阶段完成替换的;

2.宏定义只是一个简单的替换、是无脑替换;

4.4 变量

在程序运行的过程中,值允许发生改变的量。

变量的大小:取决于定义变量的类型

变量的名字:是一个标识符,要符合标识符命名规范

4.4.1 变量的初始化和赋值

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//初始化:在定义变量的同时,给变量赋一个初值
	int v1 = 1314;
	printf("v1 = %d\n", v1);//1314

	//如果定义变量没有初始化 里面就都是随机值
	int v2;
	printf("v2 = %d\n", v2);//不确定的值

	//变量是可以被重新赋值的
	v3 = 520;	// = 赋值运算符  将右边的值赋值给左边
	printf("v3 = %d\n", v3);//520
	v3 = v1;	//也可以使用变量给变量赋值
	printf("v3 = %d\n", v3);//1314
	
	//一行中可以定义多个变量 用逗号分隔
	int v4,v5,v6;

	//同一个作用域(同一个{})内 不允许定义重名的变量
	//int v4; //错误的 重名了

	return 0;
}

4.4.2 强制类型转换

在某次运算中,通过某种方式将变量的类型转换成其他类型来参与本次运算。

变量本身的类型不会改变,只是在本次运算中使用其他类型参与运算了。

注意:强制类型转换是不安全的,要谨慎使用。

小的类型转大的类型一般没问题,但是大的类型转小的类型就可能会出现数据的截断和丢失。

1. 显式强转

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int m = 5;
	int n = 2;
	float ret1 = m/n;	//这种写法 因为 m 和 n本身是 int类型 所以运算的结果也是int类型
						//m/n 得到的结果就是 2  相当于把2赋值给 ret1
	printf("ret1 = %f\n", ret1);//2.000000
	
	//将 m 和 n 的类型转换成 float 来参与本次运算  得到的结果就是 float 类型的
	//相当于 把 2.500000 赋值给 ret2
	float ret2 = (float)m/(float)n;
	printf("ret2 = %f\n", ret2);//2.500000

	//m和n的类型不会改变
	printf("m = %d  n = %d\n", m, n);//5  2

	return 0;
}

2. 隐式强转

隐式强转是编译器根据上下文自动推导的,如果编译器觉得本次转换是安全的,则正常执行,如果编译器觉得是有风险的,一般会报一个警告,具体报不报取决于编译器的严谨程度。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	float f = 3.1415;
	int m = f;	//这种操作是 取整 的操作  会舍弃小数部分
	printf("f = %f  m = %d\n", f, m);//3.141500  3

	return 0;
}

注意:有符号数和无符号数参与运算时,会将有符号数转换成无符号数参与运算。

五、运算符

运算符就是一个符号,是用来多个值之间进行运算的,可以连接多个表达式。

表达式是由 运算符 运算量 标点符号 等组成的一个有效序列,用来说明一个运算过程。

5.1 运算符的分类

算数运算符: +    -    *     /     %(模除,就是取余数的意思)

自增自减运算符:++      --

关系运算符: >    <     >=    <=     !=    ==

逻辑运算符: &&         ||         !

位运算符: &     |     ^     ~     <<    >>

条件运算符(三目运算符): ?    :

赋值运算符:=    +=    -=     *= ... (复合赋值运算符)

sizeof运算符        sizeof(  )

逗号运算符: (,,,) ----不常用

5.2 算术运算符

+ - * / %(模除,就是取余数的意思)

注意:模除要求左右操作数必须是整数。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int m = 10;
	int n = 3;
	printf("m + n = %d\n", m + n);
	printf("m - n = %d\n", m - n);
	printf("m * n = %d\n", m * n);
	printf("m / n = %lf\n", (double)m / (double)n);//整型做除法 需要强转
	printf("m %% n = %d\n", m % n);				//想输出% 需要敲两个%

	double a = 5.28;
	double b = 3.14;
	printf("a + b = %lf\n", a + b);
	printf("a - b = %lf\n", a - b);
	printf("a * b = %lf\n", a * b);
	printf("a / b = %lf\n", a / b);
	//printf("a %% b = %lf\n", a % b);//% 要求左右操作数必须是 整数

	return 0;
}

5.3 自增自减运算符

++      --

a++  <==>   a = a+1        ++a  <==>   a = a+1

前加加和后加加 变量的值都加了1 但是表达式的结果是不一样的

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = ++a;//++a表达式的结果 是 加之后的值
	printf("a = %d  b = %d\n", a, b);//11 11

	int m = 10;
	int n = m++;//a++表达式的结果 是 加之前的值
	printf("m = %d  n = %d\n", m, n);//11 10

	return 0;
}

5.4 关系运算符

>     <     >=     <=     !=     ==

关系运算符一般是用于比较大小关系的,表达式的结果是一个 bool 类型,bool类型只有两种值 0 假 非0 真,一般多用于控制语句中。

//if else 语句说明
if(表达式){
    代码块1;
}else{
    代码块2;
}
//执行逻辑,如果表达式为真 则执行 代码块1 否则执行代码块2

5.5 逻辑运算符

逻辑运算符是用来连接多个由关系运算符组成的表达式的。表达式的结果 也是一个 bool 类型

&&      逻辑与      表示并且的意思        全真则真,有假为假

||         逻辑或      表示或者的意思        有真为真,全假为假

        逻辑非      表示逻辑取反            真变假 假变真

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 0;
	int b = 0;
	scanf("%d%d", &a, &b);
	//逻辑与
	if(a >= 0 && b >= 0){
		printf("yes\n");
	}else{
		printf("no\n");
	}
	//逻辑或
	if(a >= 0 || b >= 0){
		printf("yes\n");
	}else{
		printf("no\n");
	}
	//逻辑非
	if( !(a>b) ){
		printf("yes\n");
	}else{
		printf("no\n");
	}
	return 0;
}

注意:

C语言中不允许使用这样的写法 (10<x<20)需要改成(x>10 && x<20)

逻辑运算符的短路原则:

逻辑与连接的多个表达式,如果遇到某个表达式为假了,足以判断整个表达式为假,后面的表达式就都不执行了

逻辑或连接的多个表达式,如果遇到某个表达式为真了,足以判断整个表达式为真,后面的表达式就都不执行了

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 10, b = 20;, c = 30;
	int ret = 0;
	ret = (a>0 && b<0 && ++c);//因为b<0为假 所以++c 不执行
	printf("ret = %d  a = %d  b = %d  c = %d\n", ret, a, b, c);//0 10 20 30

	a = 10;
	b = 20;
	c = 30;
	ret = 0;
	ret = (a<0 || ++b || ++c);//a<0为假 则执行++b  ++b为真 所以不执行 ++c
	printf("ret = %d  a = %d  b = %d  c = %d\n", ret, a, b, c);//1 10 21 30

	return 0;
}

5.6 位运算符

位运算是针对二进制而言的,位指的是bit位,一般情况下涉及位运算使用的都是无符号

&         按位与         按位运算 全1为1 有0为0

|          按位或          按位运算 有1为1 全0为0

^         按位异或       按位运算 不同为1 相同为0

~         按位取反       按位运算 0变1 1变0

<<       按位左移       整个数据向左移动 低位补0 舍弃高位

>>       按位右移       整个数据向右移动 高位补0 舍弃低位

#include <stdio.h>

int main(int argc, const char *argv[])
{
	unsigned char a = 0xa5;
	unsigned char b = 0x97;
	unsigned char c = 0;
	
	c = a & b;  //按位与
	//a: 10100101
	//b: 10010111
	//c: 10000101 -->0x85
	printf("a & b = %#x\n", c);//0x85

	c = a | b;   //按位或
	//a: 10100101
	//b: 10010111
	//c: 10110111 -->0xB7
	printf("a | b = %#x\n", c);//0xB7

	c = a ^ b;	//按位异或
	//a: 10100101
	//b: 10010111
	//c: 00110010 -->0x32
	printf("a ^ b = %#x\n", c);//0x32

	c = ~a;	    //按位取反
	//a: 10100101
	//c: 01011010 -->0x5A
	printf("~a = %#x\n", c);//0x5A

	c = a<<2;	//按位左移
	//a: 10100101
	//c: 10010100 -->0x94
	printf("a<<2 = %#x\n", c);//0x94

	c = a>>2;	//按位右移
	//a: 10100101
	//c: 00101001 -->0x29
	printf("a>>2 = %#x\n", c);//0x29

	return 0;
}

5.7 条件运算符

表达式1   ? 表达式2   : 表达式3;

执行逻辑:先执行表达式1,如果表达式1为真,则执行表达式2,否则执行表达式3.

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = 20;
	int c = 0;
	c = (a<b ? ++a : ++b);
	printf("c = %d  a = %d  b = %d\n", c, a, b);//11 11 20
	
    return 0;
}

5.8 赋值运算符

        =

一般形式:左 = 右        表示将右边的值赋值给左边,注意 = 和 == 的区别。

复合赋值运算符

 5.9 sizeof运算符

sizeof是用来计算变量或者类型占用的内存空间的大小的。单位 字节

#include <stdio.h>

int main(int argc, const char *argv[])
{
	char a;
	short b;
	int c;
	long d;
	long long e;
	float f;
	double g;

	printf("sizeof(char) = %ld  sizeof(a) = %ld\n", sizeof(char), sizeof(a));//1
	printf("sizeof(short) = %ld  sizeof(b) = %ld\n", sizeof(short), sizeof(b));//2
	printf("sizeof(int) = %ld  sizeof(c) = %ld\n", sizeof(int), sizeof(c));//4
	printf("sizeof(long) = %ld  sizeof(d) = %ld\n", sizeof(long), sizeof(d));//4 或 8
	printf("sizeof(long long) = %ld  sizeof(e) = %ld\n", sizeof(long long),sizeof(e));//8
	printf("sizeof(float) = %ld  sizeof(f) = %ld\n", sizeof(float), sizeof(f));//4
	printf("sizeof(double) = %ld  sizeof(g) = %ld\n", sizeof(double), sizeof(g));//8

	long ret1 = sizeof(int);//可以保存sizeof计算的结果
	printf("ret1 = %ld\n", ret1);

	int ret2 = sizeof(int);//这样写也可以 会做隐式强转
	printf("ret2 = %d\n", ret2);

	return 0;
}

5.10 逗号运算符

(   ,   ,   ,   ) ----不常用,了解即可

用逗号连接多个表达式,执行逻辑就是从左到右依次运算,最后一个表达式的结果就是整个逗号运算符表达式的结果。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 0;

	d = (++a, ++b, ++c);//从左到右依次运算 ++c的结果 就是整个表达式的结果
	printf("a = %d,  b = %d,  c = %d,  d= %d\n", a, b, c, d);//11 21 31 31

	return 0;
}

5.11 运算符的优先级

优先级顺口溜:   单算移关与,异或逻条赋。

六、常用的输入输出函数

6.1 putchar/getchar

1.putchar

功能:向终端输出一个字符

头文件:#include

函数原型:int putchar(int c);

参数: c 用来保存要输出字符的ascii码的

#include <stdio.h>

int main(int argc, const char *argv[])
{
	putchar(65);//A    可以传 字符的 ascii码
	putchar(10);

	putchar('B');//B	也可以传字符常量
	putchar('\n');

	int num = 67;	 也可传保存ascii码的变量
	putchar(num);//C
	putchar('\n');

	putchar('A'+3);//D	也可以传一个表达式
	putchar(10);

	return 0;
}

2. getchar

功能:在终端获取一个字符

头文件:#include

函数原型:int getchar(void);

返回值: 输入的字符

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//当多个getchar连续使用时 要注意清理垃圾字符
	//所谓的垃圾字符 就是我们不想得到的 回车 空格 等
	char v1=0, v2=0, v3=0;
	v1 = getchar();
	getchar();//用来吃掉垃圾字符
	v2 = getchar();
	getchar();//用来吃掉垃圾字符
	v3 = getchar();
	getchar();//用来吃掉垃圾字符
	printf("v1 = [%d] [%c]\n", v1, v1);
	printf("v2 = [%d] [%c]\n", v2, v2);
	printf("v3 = [%d] [%c]\n", v3, v3);
	return 0;
}

6.2 puts/gets

1. puts

功能:向终端输出一个字符串

头文件:#include

函数原型:int puts(const char *s);

参数: s 要输出的字符串的首地址

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//可以直接输出字符串常量,puts 是自带换行符的
	puts("hello");//hello

	char str[32] = "beijing";//也可以输出保存字符串的数组
	puts(str);//beijing

	//puts遇到 '\0' 就停止了 后面的字符 不再继续输出
	puts("abcd\0efg");//abcd

	return 0;
}

2. gets

功能:在终端获取一个字符串

头文件:#include

函数原型:char *gets(char *s);

参数:s 用来保存输入的字符串的缓冲区的首地址

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//注意:使用gets 要保证用来保存字符串的数组足够大 否则会出现越界访问
	char s1[32] = {0};//定义了一个数组 并全部初始化成0

	gets(s1);
	puts(s1);

	return 0;
}

6.3 printf/scanf

1. printf

功能:按照指定的格式,向终端输出一个字符串----格式化输出

头文件:#include

函数原型:int printf(const char *format, ...);

%c         输出字符

%d         输出整型,有符号

%u         输出整型,无符号

%o         输出八进制

%x         输出十六进制

%f         浮点型

%e         指数形式

#           输出前导符

.n          输出n位小数 n是一个数字

m          m是一个数字,可以用来指定输出的数据的位宽

0           如果数据不足指定的位宽 则使用0填充

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int hour = 10;
	int min = 9;
	int sec = 5;

	printf("%d:%d:%d\n", hour, min, sec);// 10:9:5
	printf("%2d:%2d:%2d\n", hour, min, sec);// 10: 9: 5
	printf("%02d:%02d:%02d\n", hour, min, sec);// 10:09:05

	int value = 1234;
	printf("%2d\n", value);//1234  如果指定的位宽不够 以实际为准

	printf("%10d\n", value);//默认右对齐
	printf("%-10d\n", value);//左对齐

	printf("%+d\n", value);// +1234
	value = -100;
	printf("%+d\n", value);// -100  负数会忽略 +

	return 0;
}

2. scanf

功能:按照指定的格式,在终端获取数据----格式化输入

头文件:#include

函数原型:int scanf(const char *format, ...);

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int m = 0;
	int n = 0;
	//scanf 可以连续获取多个数据
	//输入时 使用 空格 或者 回车 分隔
	scanf("%d%d", &m, &n);
	printf("m = %d, n = %d\n", m, n);
	scanf("%d:%d", &m, &n);//如果是这样写格式 输入时就必须用 : 分隔
	printf("m = %d, n = %d\n", m, n);

	char str[32];
	//scanf("%s", str);//数组名就是首地址  无需加  &
	//printf("%s\n", str);
	//scanf不能输入带有空格的字符串
	//如果想输入带有空格的字符串  可以使用   gets
	//或者使用正则表达式也行 --不常用
	printf("%s\n", str);

	return 0;
}

6.4 垃圾字符的处理

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//场景1:多次连续调用 getchar() 时
	//处理方式:在每两次调用 getchar之间加一个getchar 用来吃掉垃圾字符 回车
#if 0
	//场景2:多次使用scanf连续获取字符时
	//处理方式:在每两次调用 scanf之间加一个getchar 用来吃掉垃圾字符 回车
	char a,b,c;
	scanf("%c", &a);
	getchar();
	scanf("%c", &b);
	getchar();
	scanf("%c", &c);
	printf("a = [%d] [%c]\n", a, a);
	printf("b = [%d] [%c]\n", b, b);
	printf("c = [%d] [%c]\n", c, c);
#endif
	//场景3:使用scanf 一次获取多个字符时
	char a,b,c;
	//scanf("%c%c%c", &a, &b, &c);
	//处理方式1:使用抑制符来处理  %*c
	//抑制符的作用是获取数据 但是 不赋值给后面的变量
	//注意:抑制符是一定要吃掉一个字符的 他并不管是不是垃圾字符
	//scanf("%c%*c%c%*c%c", &a, &b, &c);

	//处理方式2: 可以在格式中使用空格来处理--推荐的处理方式
		//空格可以吃掉多个空格 或者 回车
	scanf("%c %c %c", &a, &b, &c);
	printf("a = [%d] [%c]\n", a, a);
	printf("b = [%d] [%c]\n", b, b);
	printf("c = [%d] [%c]\n", c, c);
    //注意  %d 是不会有垃圾字符的问题的
	
	return 0;
}

七、分支控制语句

7.1 if...else 语句

7.1.1格式及执行逻辑

1.简化格式
	if(表达式){
		代码块;
	}
	执行逻辑:先执行表达式,如果表达式为真,则执行代码块,否则就不执行代码块
		
	if(表达式){
		代码块1;
	}else{
		代码块2;
	}
	执行逻辑:先执行表达式,如果表达式为真,则执行代码块1,否则执行代码块2

2.阶梯形式
	if(表达式1){
		代码块1;
	}else if(表达式2){
		代码块2;
	}else if(表达式n){
		代码块n;
	}else{
		其他分支;
	}
	执行逻辑:先执行表达式1,如果为真,则执行代码块1,
		     否则执行表达式2,如果为真,则执行代码块2,
		     依次类推,如果前面的表达式都为假,则执行else分支

3.嵌套格式
	if(表达式1){
		if(表达式11){
			代码块11;
		}else{
			其他分支1;
		}
	}else if(表达式2){
		if(表达式21){
			代码块21;
		}else{
			其他分支2;
		}
	}
    执行逻辑:就是前面阶梯形式的嵌套使用

7.1.2 if...else语句注意事项

1.如果if..else语句的代码块只有一行,那么 {} 可以不用写;

        如果不加{} if..else 只对下面的一行指令生效

2.else必须要有if与之对应,不能单独出现

3.else与同一层次的前面最近的if结合

4.关于if..else语句的表达式:

一般情况下都是有关系运算符和逻辑运算符组成的,用来表示真假

如果是特殊情况,注意下面的用法

if(a = 10)         //条件恒为真

if(a = 0)         //条件恒为假

        //建议:变量和常量判断相等时 将常量放在左边 这样少写等号的时候 编译会报错

if(a=b)         //这时表达式的结果取决于 b的值 b为0 假 b为非0 真

if(a)         //这时表达式的结果取决于 a的值 a为0 假 a为非0 真 等价于 if(a!=0)

if(!a)         //这时表达式的结果取决于 a的值 a为0 真 a为非0 假 等价于 if(a==0)

5.分支控制语句只会选择一条分支执行

int a = 10;
if(a > 0){
    printf("11\n");
    a = -10;
}else if(a < 0){
    printf("22\n");
}
//上面的代码只输出 11

7.2 switch...case 语句

switch(表达式){
	case 常量表达式1:
		代码块1;
		break;
	case 常量表达式2:
		代码块2;
		break;
	case 常量表达式n:
		代码块n;
		break;
	default:
		其他分支;
		break;
}
执行逻辑:先执行表达式,判断表达式的结果属于哪个case
         属于哪个就执行哪个case的代码块

注意事项

1.switch后面括号的例表达式就是我们要判断的值,每个case后面的常量表达式就是

前面表达式所有可能的结果。

2.break的作用是 执行完某一个分支后,就结束整个switch..case语句

如果没有break,程序会继续执行下面case的代码块,直到遇到break或者

整个switch..case语句结束,----case击穿

3.default表示其他分支,如果前面的所有case都不满足,则执行default分支的代码块

作用相当于if..else语句中的 else 分支,如果不关心其他分支的情况 整个default分支可以不写

八、循环控制语句

8.1 goto循环

代码块1;
    goto NEXT;
    代码块2;
NEXT:    //标签
    代码块3;
执行逻辑:
先执行代码块1,然后遇到goto 直接跳转到对应的标签向下执行,代码块2被跳过,不执行。
标签名也是一个标识符,需要符合命名规范,一般情况下都采用大写

8.2 while 循环

while(表达式){
    代码块;
}
执行逻辑
先执行表达式,如果表达式为真,则执行代码块
然后再执行表达式,如果还为真,则继续执行代码块。。
直到表达式为假,循环立即结束。。
表达式的用法:和 if 语句的表达式用法一样
注意:代码块中一般要有能改变表达式真假的语句,否则就是死循环了。

8.3 do...while 循环

do{
    代码块;
}while(表达式);    //注意:最后要有一个分号
执行逻辑
先执行代码块,然后执行表达式,如果表达式为真,则继续执行代码块
然后再执行表达式,如果还为真,则继续执行代码块,直到表达式为假,循环立即结束

do..while 和 while的区别

while 先判断 后执行,do..while 先执行 后判断

8.4 for 循环

for(表达式1; 表达式2; 表达式3){
    代码块;
}
执行逻辑
先执行表达式1,然后执行表达式2,如果表达式2为真,则执行代码块
然后执行表达式3,然后再执行表达式2,如果还为真,则继续执行代码块和表达式3
直到表达式2为假,循环立即结束。

如果有需求,表达式1和表达式3可以使用逗号分隔 写多个

for循环的每个表达式 如果不需要,都可以不写,但是两个分号必须写。

8.5 死循环

所谓的死循环就是条件恒为真,循环体一直执行的逻辑。有些业务场景需要程序一直执行,就需要用到死循环。在linux系统运行中遇到死循环可以按 ctrl+c 来结束程序

while(1){   //----常用的用法
    循环体;
}

for(;;){	//表达式可以不写,但是两个分号必须写
    循环体;
}

8.6 辅助控制关键字

8.6.1 break

break可以用在switch..case 语句中,表示执行完某个分支就立即结束整个switch..case语句,break也可以用在循环中,表示立即结束本层循环

8.6.2 continue

continue只能用在循环中,表示立即结束本层的本次循环

8.6.3 return

return 如果用在函数中,表示返回函数执行的结果;如果用在主函数中,表示立即结束整个程序。

九、数组

数组是用来保存一组相同数据类型的数据的,是一个构造类型,数组中的每个数据叫做数组的元素,或者叫做数组的成员。数组在内存上需要分配一块连续的空间来存储数据,不管几维数组,成员之间都是连续的。

9.1 一维数组

存储类型  数据类型  数组名[下标];

存储类型:不写默认就是 auto

数据类型:指的是数组中每个元素的数据类型,可以是基本类型、也可以是构造类型(数组除外)

数组名: 是一个标识符,要符合标识符的命名规范

下标:在定义数组的时候,下标是个常量,用来表示数组的元素的个数,在其他场景下,下标表示访问数组中的第几个元素,一维数组的下标只有一个。

int  s[5];
//定义了一个一维数组 数组名叫s 数组中每个元素都是一个 int类型的变量
//数组中共有5个元素

9.1.1 一维数组的性质

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//定义了一个一维数组 数组名是s 数组中共有5各元素
	//每个元素都是一个int类型的变量
	int s[5];

	//数组访问元素的方式   数组名[下标]
	//注意,下标是从0开始的
	//当取出数组的元素操作时 和 操作单个的变量就是一样的了
	//给数组元素赋值
	s[0] = 100;
	s[1] = 200;
	s[2] = 300;
	s[3] = 400;
	s[4] = 500;
	//读取数组元素的值
	printf("s[0] = %d\n", s[0]);//100
	printf("s[1] = %d\n", s[1]);//200
	printf("s[2] = %d\n", s[2]);//300
	printf("s[3] = %d\n", s[3]);//400
	printf("s[4] = %d\n", s[4]);//500

	//数组名是一个常量
	//s = 1314;	//错误的
	//s++;		//错误的  s = s+1;

	//数组的元素在内存上是连续的
	//printf  %p 可以按照16进制打印地址编号
	// &变量名  & 取地址符  可以获取变量的地址
	printf("&s[0] = %p\n", &s[0]);//连续的
	printf("&s[1] = %p\n", &s[1]);
	printf("&s[2] = %p\n", &s[2]);
	printf("&s[3] = %p\n", &s[3]);
	printf("&s[4] = %p\n", &s[4]);

    //数组的长度:元素的个数
    //数组的大小:占用的内存空间的大小
	//一维数组的大小 = 元素的个数 * 单个元素的大小
	printf("sizeof(s) = %ld\n", sizeof(s));//20 == 5*sizeof(int)
	
	//数组的元素是变量 允许被重新赋值
	s[0] = 520;
	printf("s[0] = %d\n", s[0]);//520

	//下标可以是一个变量
	int m = 1;
	s[m] = 1234;
	printf("s[m] = %d\n", s[m]);//1234
	//下标可以是一个表达式
	s[s[0]-519] = 1314;
	printf("s[1] = %d\n", s[1]);//1314

	//遍历一维数组 方式1
	int i = 0;
	for(i = 0; i < 5; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	//遍历一维数组 方式
	for(i = 0; i < sizeof(s)/sizeof(s[0]); i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	return 0;
}

9.1.2 一维数组的初始化

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//如果一维数组没有初始化,里面存放的都是随机值
	//int s[5];
	
	//一维数组初始化的方式
	//完全初始化
	//int s[5] = {10, 20, 30, 40, 50};
	
	//不完全初始化
	//这时是从左到右依次初始化 没有初始化的元素 会用0初始化
	int s[5] = {10, 20};
	
	//全部初始化成 0
	//int s[5] = {0};  //--最常用的用法
	
	//省略下标的初始化
	//编译器会根据后面初始化的元素的个数 来自动计算数组的长度
	//int s[] = {10, 20, 30};

    //数组一旦定义好了就没法整体赋值了,只能一位一位的赋值
    //int s2[5];
    //s2 = s; //错误的  数组名是常量 不能被赋值
    //s2[5] = {1,2,3,4,5};//错误的  相当于给数组的下标为5的那个元素赋值
                            //越界了

	int i = 0;
	for(i = 0; i < 5; i++){
		printf("%d  ", s[i]);
	}
	printf("\n");

	//关于数组访问越界的问题
	//注意:数组越界访问的问题 编译器不会检查
	//需要程序员自己检查,而且数组越界的问题是很严重的
	//越界访问的现象是不可预知的:
	//	可能正常执行
	//	可能是段错误
	//	可能修改了其他变量的数据
	//尤其是变量 或者 表达式做为下标使用的使用 要特别注意检查边界
	int hqyj[8] = {0};
	hqyj[12345] = 1314;//错误的操作
	printf("%d\n", hqyj[12345]);//错误的操作

	return 0;
}

例:

定义一个长度为10的int类型的一维数组,从终端给数组的元素赋值,找出数组中最大的值,输出最大值。

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int s[10] = {0};

	int i = 0;
	//循环给数组赋值
	for(i = 0; i < 10; i++){
		scanf("%d", &s[i]);
	}

	//遍历一维数组
	for(i = 0; i < 10; i++){
		printf("%d  ", s[i]);
	}
	putchar(10);

	//保存最大值的下标
	int max_index = 0;
	for(i = 1; i < 10; i++){
		if(s[max_index] < s[i]){
			max_index = i;
		}
	}
	//当循环结束的时候 max_index 中记录的就是最大值的下标
	printf("max_value = %d\n", s[max_index]);

	return 0;
}

9.2 二维数组

二维数组有两个下标。

存储类型  数据类型  数组名[行数][列数];

二维数组虽然有行号和列号,但是所有的元素在内存上也是连续的,

列数:决定了按行访问元素时的跨度。

 9.2.1 二维数组的性质

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//定义了一个二维数组 数组名叫s  数组中共有3行4列共计12个元素
	//每个元素都是一个 int 类型的 变量
	int s[3][4];

	//二维数组访问元素   数组名[行号][列号]
	//注意:行号和列号都是从 0 开始的
	//二维数组取出元素后的操作 和 操作单个变量也是一样的
	s[0][0] = 520;
	s[0][1] = 1314;
	s[2][2] = 1234;
	printf("s[0][0] = %d\n", s[0][0]);//520
	printf("s[0][1] = %d\n", s[0][1]);//1314
	printf("s[2][2] = %d\n", s[2][2]);//1234

	//二维数组的大小 = 行数*列数*单个元素的大小
	printf("sizeof(s) = %ld\n", sizeof(s));//48  == 3 * 4 * sizeof(int)

	//二维数组的数组名 也是一个常量
	//也不能被赋值  不能++
	//int s2[3][4];
	//s2 = s;	//错误的
	//s2++;	//错误的
	
	//二维数组的元素再内存上也是连续的
	printf("%p\n", &s[0][3]);	
	printf("%p\n", &s[1][0]);//连续的 一次相差一个 int

	printf("%p\n", &s[1][3]);	
	printf("%p\n", &s[2][0]);//连续的 一次相差一个 int

	//遍历二维数组
	int i = 0;
	int j = 0;
	//外层循环控制行数
	for(i = 0; i < 3; i++){
		//内层循环控制列数
		for(j = 0; j < 4; j++){
			printf("%d  ", s[i][j]);
		}
		printf("\n");
	}

	return 0;
}

9.2.2 二维数组的初始化

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//如果二维没有初始化 里面也都是随机值
	int s[3][4];
	
	//二维数组的初始化格式
	//以行为单位
		//完全初始化
		int s[3][4] = {{1,2,3,4},\
						{5,6,7,8},\
						{9,10,11,12}};
		//不完全初始化 没有初始化的位 也是用0初始化
		int s[3][4] = {{1,2},\
						{5,6},\
						{9}};

	//不以行为单位
		//完全初始化
		int s[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
		//不完全初始化 没有初始化的位 也是用0初始化
		int s[3][4] = {1,2,3,4,5,6};
	
	//全部初始化成0  --常用的用法
	int s[3][4] = {0};
	
	//省略下标的初始化
	//行数可以省略 但是列数不能省略 因为列数决定按行操作时的跨度
	//如果给定的元素不够整行 也按照整行分配空间
	//没有给定的位也是用 0 初始化
	int s[][4] = {1,2,3,4,5,6,7,8,9};

	printf("sizeof(s) = %ld\n", sizeof(s));//48 说明是按照整行分配的空间

	//遍历二维数组
	int i = 0;
	int j = 0;
	for(i = 0; i < 3; i++){
		for(j = 0; j < 4; j++){
			printf("%d  ", s[i][j]);
		}
		putchar(10);
	}

	//数组一旦定义好了就不能整体赋值了 只能一位一位的赋值

	return 0;
}

例:定义一个3行4列的二维数组,并以行为单位完全初始化初始化的数据随便指定,找出数组中最大的元素,及最大元素所在的行号、列号,并输出。

#include <stdio.h>

int main(){
    int s[3][4] = {{12,23,34,45},{11,22,33,44},{456,1,2,3}};
    int max_h = 0;
    int max_l = 0;
    int i = 0;
    int j = 0;
    for(i = 0; i < 3; i++){
        for(j = 0; j < 4; j++){
            if(s[i][j] > s[max_h][max_l]){
                max_h = i;
                max_l = j;
            }
        }
    }
    printf("max_value = %d  max_hang = %d max_lie = %d\n",
            s[max_h][max_l], max_h, max_l);
    
    return 0;
}

9.3 字符数组和字符串

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//字符数组的使用
	char s1[5] = {'h', 'e', 'l', 'l', 'o'};
	int i = 0;
	for(i = 0; i < 5; i++){
		printf("%c", s1[i]);
	}
	printf("\n");

	//也可以将字符串保存在字符数组中
	//注意:字符串的结尾会有一个隐藏的 '\0' 所以要多分配一个字节来存储
	//格式1
	char s2[6] = {"world"};
	//格式2//--常用
	char s3[6] = "12345";
	//格式3//--常用
	char s4[] = "www.baidu.com";//这种写法 编译器计算长度时会包括 '\0'
	printf("sizeof(s4) = %ld\n", sizeof(s4));//14

	//如果是字符串 可以使用 %s 处理
	printf("s2 = [%s]\n", s2);//world
	printf("s3 = [%s]\n", s3);//12345
	printf("s4 = [%s]\n", s4);//www.baidu.com

	//注意 %s 处理的时候是从给定的首地址开始 一直向后面找
	//找到 '\0' 才结束 如果没有 '\0'就一直找  可能会越界访问
	char s5[12] = "aabb\0ccdd";
	printf("s5 = [%s]\n", s5);//aabb
	//ccdd也在数组中 只不过通过 %s 的方式 访问不到了
	printf("s5[5] = %c\n", s5[5]);//c
	printf("s5[7] = %c\n", s5[7]);//d

	//如果不是字符串 就不能使用 %s 处理
	//因为不是字符串 就没有  '\0'
	//printf("s1 = [%s]\n", s1);//错误的 错误不可预知
	
	//下面的用法是可以的  因为不完全初始化 会用0来初始化那些没有初始化的位
	char s6[6] = {'h', 'e', 'l', 'l', 'o'}; // 0 就是 '\0'
	printf("s6 = [%s]\n", s6);

	return 0;
}

9.4 字符串处理函数

strlen     strcpy     strcat      strcmp

头文件:#include <string.h>

1.  strlen

功能:计算字符串的长度,不包括'\0'的

//可以用变量保存计算的结果
	int len = strlen(hqyj);

sizeof strlen 区别

sizeof是关键字 strlen 是函数

sizeof计算的是占用的内存的字节数 只和定义时有关 strlen计算的字符串中第一个 '\0' 之前的字符的个数

2.    strcpy

功能:字符串的复制

strcpy(s1, s2);//将s2 拷贝给 s1

注意:目标字符串指向的缓冲区要保证足够大 否则会越界

3.    strcat

功能:字符串的拼接

    char s1[32] = "beijingnihao";
	char s2[32] = "hello";
	strcat(s1, s2);//将s2 追加到 s1后面 会覆盖 s1的'\0'
	printf("操作后 s1 = [%s]\n", s1);//beijingnihaohello
	printf("操作后 s2 = [%s]\n", s2);//hello

注意:目标字符串指向的缓冲区要保证足够大 否则会越界

4.       strcmp

功能:字符串的比较

比较方式:

两个字符串逐个的比较每个字符的ascii码,直到出现大小关系就立即返回,两个字符串中第一次出现 '\0' 之前的字符都相等 才认为两个字符串相等

注意:比的不是长度

返回值:s1-s2  scii码的差值

    char s1[32] = {0};
	char s2[32] = {0};
	gets(s1);
	gets(s2);
    int ret = strcmp(s1, s2);
	if(ret > 0){
		printf("s1>s2\n");
	}else if(ret<0){
		printf("s1<s2\n");
	}else{
		printf("s1==s2\n");
	}

十、指针

内存中每个字节的空间都有一个编号,这个编号就是指针,也叫作地址。

保存指针的变量叫做 指针变量

通常交流时所说的指针表示指针变量,地址表示内存编号。

指针的相关操作

&        取地址符,获取变量在内存上占用的空间的地址编号,对于多字节的变量取到的是编号最小的那个地址,叫做首地址。

*        在定义指针变量的时候,只是起到标识的作用,表示定义的是一个指针变量,其他场景下,都表示操作指针保存的那块内存空间。

定义指针变量的格式

存储类型  数据类型  *指针变量名;

指针和变量的关系图

 左侧编号表示内存的地址编号

10.1 指针的基本使用

#include <stdio.h>

int main(int argc, const char *argv[])
{
	//当程序执行到定义变量的语句时
	//会根据变量的类型分配对应大小的内存空间 用来存储数据
	int a = 10;

	// 通过 & 可以获取变量的地址
	// 对于多字节的变量取到的是编号最小的那个地址 叫做首地址
	printf("&a = %p\n", &a);

	//地址的范围
	//32位系统  0x0 ~ 0xFFFFFFFF 4字节
	//64位系统  0x0 ~ 0xFFFFFFFFFFFFFFFF 8字节

	return 0;
}

可以定义一个指针变量 来保存变量的地址 ,指针保存了变量的地址 称之为 指针 p1 指向变量a

int a = 10;
int *p1 = &a;  //定义了一个 int * 类型的指针变量 p1 保存a的地址

指针保存了变量的地址后 就可以通过指针 操作指向的空间的内容了

    //读操作
	printf("a = %d,  *p1 = %d\n", a, *p1);//10 10
	//写操作
	*p1 = 520;
	printf("a = %d,  *p1 = %d\n", a, *p1);//520 520

地址本质也是一个整数,但是不能用普通的变量来存储地址

long value = &a; 
printf("value = %p\n", value);//保存是可以保存 
*value = 1314;//但是普通变量不能取 * 操作

指针的类型决定了指针存保存的地址开始一共能访问多少个字节

int * 指针          访问 4字节

char * 指针       访问 1字节

一般情况下 指针的类型要和指向的变量的类型 保持一致,目的是为了操作空间的大小一致

指针变量也是变量值允许改变,指针的值改变我们称为改变了指针的指向

int a = 10;
int *p1 = &a;  //定义了一个 int * 类型的指针变量 p1 保存a的地址
int b = 1234;
p1 = &b;       //指针的值改变 我们称为改变了指针的指向

注意:常量没有地址可言,int *p2 = &100;//错误的

如果指针没有初始化里面存的也是随机值 --就是一个随机的地址,这种指向不确定的指针 我们称之为 野指针 ,野指针是有害的,对野指针的操作 错误是不可预知的,要注意 防止野指针。如果定义指针时没确定指向 可以先 用 NULL 初始化指针,这种指针叫做 空指针。

int *p7 = NULL;	// NULL 本质是 (void *)0

10.2 指针的运算

指针的运算本质就是指针保存的地址作为运算量来参与运算,所以,能进行的运算就是有限的。只有相同类型的指针变量做运算才有意义。

指针做算数运算时 一般在连续的空间上才有意义
两个指针做差得到的结果是 相差的指针的类型 的个数,和指针指向的变量的类型无关

    int s[5] = {10,20,30,40,50};
	int *p1 = &s[0];
	int *p2 = &s[3];
	printf("p2 - p1 = %ld\n", p2 - p1);//3 相差是3个int

指针的强转 是 安全的 因为指针的大小是固定的

    char *q1 = (char *)&s[0];
	char *q2 = (char *)&s[3];
    printf("p1 = %p   q1 = %p\n", p1, q1);//一样的
	printf("p2 = %p   q2 = %p\n", p2, q2);//一样的
	printf("q2 - q1 = %ld\n", q2 - q1);//12 相差是12个char

一个指针加上一个整数 n 得到的结果是加上 n个数据类型的大小

    int s[5] = {10,20,30,40,50};
	int *p3 = &s[0];
	int *p4 = NULL;
	p4 = p3 + 2;
	printf("p3 = %p, p4 = %p\n", p3, p4);//相差8  2个int

关系运算

    int s[5] = {10,20,30,40,50};
	int *p7 = &s[0];
	int *p8 = &s[4];
	if(p8 > p7){
		printf("yes\n");
	}else{
		printf("no\n");
	}

大小端存储问题(字节序问题):

CPU和操作系统不同,对多字节数据存储的方式也不同。

小端存储:地址低位存储数据低位,地址高位存储数据高位

大端存储:地址低位存储数据高位,地址高位存储数据低位

 10.3 指针和一维数组

数组名就是数组的首地址,数组名[下标]就是访问成员的本质,以数组首地址为基准偏移 然后取 * 操作。

    int s1[5] = {10, 20, 30, 40, 50};
    s1[0] = 1314;
    printf("s1[0] = %d,  *s1 = %d\n", s1[0], *s1);//1314 1314
	//也就是说 数组名这个地址 的操作空间是一个元素
	printf("s1[1] = %d, *(s1+1) = %d\n", s1[1], *(s1+1));//20 20

可以定义一个指针来保存数组的首地址

    int *p1 = s1; //数组名 就是首地址 这种是最常用的写法
	int *p2 = &s1[0];//这种写法也可以
	
	int *p3 = &s1;//注意:这种用法 保存的地址的值是一样的 
					//但是&s1 相当于给指针升维了,不要对数组名取地址!!!

指针指向数组后 就可以通过指针来操作数组的元素了,指针指向数组后 有如下的等价关系

s1[i]    <==>    *(s1+i)    <==>    p1[i]    <==>    *(p1+i)

    *p1 = 520;
	*(p1+1) = 1314;
	p1[2] = 1234;

指针指向数组后 p1 和 s1 的区别

    //p1 是指针  是变量  可以改变指向
	int s2[5] = {10, 20, 30, 40, 50};
	p1 = s2;
	//s1 是数组名 是地址常量 不能被赋值
	//s1 = s2;//错误的
	
	p1++;//正确的  指针的指向向后偏移 一个 int
	s1++;//错误的 s1 是常量

10.4 指针和二维数组

二维数组的数组名也是一个地址常量,也就是数组的首地址

二维数组数组名的操作空间是一整行元素 +1 加的就是一整行,我们也称之为 行指针

    int s[3][4] = {{1,2,3,4},
					{5,6,7,8},
					{9,10,11,12}};

    printf("s+1 = %p\n", s+1);//依次相差 16  4 * sizeof(int)
	printf("s+2 = %p\n", s+2);

* 操作可以给行指针降维,本来操作空间是一行的指针降维成操作空间是一个元素的指针

对降维之后的指针再取 * 操作 才是操作里面的数据

    printf("*s = %p\n", *s);//依次相差 4  也就是一个int
	printf("*s+1 = %p\n", *s+1);
	printf("*s+2 = %p\n", *s+2);

	printf("*(*s+1) = %d\n", *(*s+1));//2    对降维之后的指针再取 * 操作 才是操作里面的数据
	printf("*(*(s+0)+1) = %d\n", *(*(s+0)+1));//2
	printf("*(*(s+2)+1) = %d\n", *(*(s+2)+1));//10
    //s[i]   <==> *(s+i)
	//s[i][j] <==> *(*(s+i)+j) <==> *(s[i]+j)

不能使用普通的指针来保存二维数组的首地址,原因是二维数组的数组名是一个行指针操作空间是一整行元素已经超过了普通的指针的操作范围了,普通的指针操作空间都是一个元素。

10.5 数组指针

本质是一个指针,指向一个二维数组,也叫作行指针,数组指针一般多用于函数传参,二维数组作为参数传递时。

格式:

数据类型  (*数组指针名)[列宽];

定义一个数组指针 ,指针名叫做 p 该指针能指向 int 类型的 列宽为4 的二维数组

int (*p)[4] = s;

指针指向二维数组后 就有下面6个等价关系

s[i][j]    <==>    *(s[i]+j)    <==>    *(*(s+i)+j)

p[i][j]    <==>    *(p[i]+j)    <==>    *(*(p+i)+j)

p和s区别:         p是变量 s是常量

10.6 指针数组

本质是一个数组,数组中每个元素都是一个指针类型。

格式:

数据类型  *指针数组名[下标];

例如:

    //定义了一个 指针数组 数组名叫 p 数组中共有 4个元素
	//每个元素 都是一个 char * 类型的指针
	char *p[4] = {NULL};
	p[0] = "zhangsan";
	p[1] = "lisi";
	p[2] = "xiaoming";
	p[3] = "fulajier.fulajimiluoweiqi.pujing";

    printf("sizeof(p) = %ld\n", sizeof(p));// 4 * sizeof(char *) = 32 (64位系统)

10.7 指针和字符串

虚拟内存的分区:以32位系统为例

可以使用字符数组保存字符串,这种操作 数组s1在栈区 ,相当于将字符串常量区的 "hello world" 复制一份儿到栈区保存通过s1操作,是操作栈区的数据

    char s1[16] = "hello world";
	printf("s1 = %s\n", s1);//hello world
	
	//栈区的内存是允许修改的
	*s1 = 'H';	//正确的
	printf("s1 = %s\n", s1);//Hello world

    //栈区两个数组的地址 一定是不一样的
	char s2[16] = "hello world";
	printf("s1 = %p, s2 = %p\n", s1, s2);//不一样的

也可以直接使用指针指向字符串常量,但是注意这时指针是指向的字符串常量区的 "hello world" 字符串常量区是不允许修改的

    char *p1 = "hello world";
	printf("p1 = %s\n", p1);//hello world  读取是可以的
	//*p1 = 'H';	//错误的 执行会报段错误
	
	//不管多少个指针 只要指向的是同一个字符串常量 保存的地址就是一样的
	char *p2 = "hello world";
	printf("p1 = %p, p2 = %p\n", p1, p2);//一样的

10.8 二级指针

二级指针是用来保存一级指针的地址的

格式:

数据类型  **二级指针名;

例如:

#include <stdio.h>

int main(int argc, const char *argv[])
{
	int a = 10;
	int *p = &a;
	//不能使用一级指针保存一级指针的地址
	//但是一级指针 没法 取  ** 操作  也就是说  无法访问 p指向的空间
	
	//保存一级指针的地址 要使用二级指针
	int **q = &p;

	printf("**q = %d,  *p = %d,  a = %d\n", **q, *p, a);//一样的
	printf("*q = %p, p = %p, &a = %p\n", *q, p, &a);//一样的
	printf("q = %p, &p = %p\n", q, &p);//一样的

	//二级指针的操作空间是 一个指针的大小
	printf("q = %p, q+1 = %p\n", q, q+1);//相差 8  (64位系统)
	
	return 0;
}

变量、一级指针、二级指针的关系:

 10.9 const关键字

const用来修饰变量,表示是一个只读变量,不可以修改。--readonly

    //const 修饰变量
	const int value = 100;
	//value = 520;//错误的

const 用来修饰指针,要注意 const 和 * 的相对位置关系

const 在 * 的左边修饰的是 *p ,表示不能通过该指针修改指向空间里的内容,但指针的指向可以修改。

    int a = 10;
	int b = 20;
	const int *p = &a;
	//*p = 1314;  //错误的 不能通过指针修改指向空间的内容
	//a = 1234;  //正确的  通过变量自身可以修改
	p = &b;//正确的  指针的指向 可以修改

const 在 * 的右边修饰的是 p ,表示指针的指向不能修改,但是可以通过指针修改里面的内容

    int a = 10;
	int b = 20;
	int * const p = &a;
	//p = &b;//错误的 指针的指向不能修改
	*p = 1314;//正确的  可以通过指针修改指向空间的内容

当 * 的左边和 p 的左边都有const修饰时,const int * const p --都不能修改

十一、函数

所谓函数,就是能将实现某些功能的代码封装成代码块,想使用这个功能时,通过代码块的名字就可以调用这个功能。代码块的名字就是函数名

格式:

返回值类型  函数名(函数的参数列表){
    //函数体 -- 也就是实现功能的代码块
}

返回值的类型可以是整形、实型、空型等,函数名是一个标识符,要符合标识符的命名规范,()里面是函数的参数,如果没有可以不写,{ }里面是用来实现功能的代码块,return是函数的返回值,如果没有可以不写,或只写return

11.1 函数的调用

一个程序是由一个main函数和若干个字函数构成的

#include <stdio.h>

void hqyj(){
	printf("1234\n");
}

int main(int argc, const char *argv[])
{
	//函数一旦定义好之后 就可以被其他的函数调用了
	//调用函数的格式  函数名(参数)  如果没有参数  ()也必须写 且不能写 void
	//程序执行到调用函数的语句时 就会跳转到函数中执行 执行完函数后
	//回到调用处 继续向下执行
	//函数如果不被调用 是不会执行的
	hqyj();
	
	return 0;
}

如果把所有的函数都定义在 main 函数的上面,在main函数中调用是没有问题的。但是函数之间相互调用就可能出现不认识的情况,这时就需要用到函数的声明。
 

//函数的声明
void my_print();
void hqyj();

11.2 函数的参数

在函数中实现功能的过程中,有些值,函数内部没有,就需要调用者通过参数的方式给函数传递过去。

在定义函数的时候 () 里面的叫做函数的形式参数 --简称形参,在调用函数的时候,操作系统会给形参分配空间 然后用实参来初始化形参,在函数中使用形参 就相当于 使用了实参的值,在函数中对形参的修改 并不会直接影响到实参 因为实参和形参不在同一块内存空间上

形参加const 的作用 是不允许在函数内部修改形参的值 ,保护形参不被修改

void my_sub(const int x, const int y);//定义中形参有const 声明时也要有

11.3 函数的返回值

有些代码可能会用到函数的执行结果。如 strlen 函数返回结果给调用者,调用者可以自己决定如果处理这个执行的结果。如果需要用到函数的返回值就写 如果不需要用,也可以不写。

#include <stdio.h>

int my_add(int x, int y){
	printf("my_add\n");
	int temp = x+y;
	//如果有返回值 返回的可以是变量/常量/表达式
	//要注意要和函数名前面的类型 保持一致
	return temp;
	//函数中遇到return 就立即返回 后面的代码都不执行了	
	printf("----my_add end----\n");//不会被执行
}

int main(int argc, const char *argv[])
{
	int a = 10;
	int b = 20;
	//函数返回的结果会返回到调用处 调用者可以决定是否保存
	//以及如何处理返回值
	int ret = my_add(a, b);
	printf("ret = %d\n", ret);
	//有返回的函数 也可以不接返回值
	//不接返回值 函数也会被调用 就是没有执行结果了
	my_add(a, b);

	return 0;
}

11.4 全局和局部

全局变量: 没有被任何{ }包住的就叫做全局变量 ----如果初始化了就在data段 没初始化就在bss段

生命周期:   整个程序结束

作用域:   整个文件

局部变量:被{}扩住的变量都叫做局部变量--在栈区

生命周期:就是最近的{ }结束

作用域:当前最近的{}内部

11.5 函数的传参方式

11.5.1 全局传参

不常用

#include <stdio.h>

//因为任何一个函数都可以访问全局变量
int lvalue;
int rvalue;
int ret;

void my_add(void){
	ret = lvalue + rvalue;
}

int main(int argc, const char *argv[])
{
	lvalue = 100;
	rvalue = 200;
	my_add();
	printf("ret = %d\n", ret);//300
	return 0;
}

11.5.2 复制传参(值传递)

#include <stdio.h>
//功能:将x和y扩大10被,计算的结果赋值给z
//这种传参方式 叫做复制传参(值传递)
//不管在函数内部如何修改形参 实参都不会发生变化
//因为形参和实参不在同一块内存空间上
void my_func(int x, int y, int z){
	x = x * 10;
	y = y * 10;
	z = x+y;
	printf("x = %d  y = %d  z = %d\n", x, y, z);//100 200 300
	printf("&x = %p  &y = %p  &z = %p\n", &x, &y, &z);//和下面不一样
}

int main(int argc, const char *argv[])
{
	int m = 10;
	int n = 20;
	int ret = 0;
	my_func(m, n, ret);
	printf("m = %d  n = %d  ret = %d\n", m, n, ret);//10 20 0
	printf("&m = %p  &n = %p  &ret = %p\n", &m, &n, &ret);//和上面不一样
	return 0;
}

11.5.3 地址传参(地址传递)

#include <stdio.h>

//函数功能:将x和y扩大10被,计算的结果赋值给 实参ret
//x和y传参方式 叫做复制传参(值传递)
//而 z 的传参方式叫做地址传参
//不管在函数内部如何修改形参 实参都不会发生变化
//因为形参和实参不在同一块内存空间上
void my_func(int x, int y, int *z){//z中保存的是 实参 ret 的地址
	x = x * 10;
	y = y * 10;
	*z = x+y;//对  *z 的操作 就是对 实参 ret 的操作
	printf("x = %d  y = %d  *z = %d\n", x, y, *z);//100 200 300
	printf("&x = %p  &y = %p  z = %p\n", &x, &y, z);//&x &y 和 下面 &m &n 不一样
													//但是 z 就是 &ret
}

int main(int argc, const char *argv[])
{
	int m = 10;
	int n = 20;
	int ret = 0;
	my_func(m, n, &ret);
	printf("m = %d  n = %d  ret = %d\n", m, n, ret);//10 20 300
	printf("&m = %p  &n = %p  &ret = %p\n", &m, &n, &ret);

	return 0;
}

11.6 数组的传参方式

字符串传参只需要传递首地址即可, 因为字符串有'\0'作为结束的标志

一维整型数组传参,既要传首地址,还要传数组的长度。

void print_arr(int *p, int len)//--常用的用法

二维数组的传参方式

void print_arr(int (*p)[4], int hang, int lie)

11.7 main函数的参数

#include <stdio.h>

//argv 本质是一个指针 指向一个成员都是char * 类型的指针数组
int main(int argc, const char *argv[])
{
	//argc 是命令行执行程序时参数的个数 (包括可执行文件名)
	printf("argc = %d\n", argc);

	//如果执行 命令时 使用的是  ./a.out file1 file2
	//printf("argv[0] = %s\n", argv[0]);// "./a.out"
	//printf("argv[1] = %s\n", argv[1]);// "file1"
	//printf("argv[2] = %s\n", argv[2]);// "file2"
	
	//遍历命令行的所有参数
	int i = 0;
	for(i = 0; i < argc; i++){
		printf("%s\n", argv[i]);
	}
	
	return 0;
}

11.8 指针函数

本质是一个函数,返回值是一个指针类型。

注意:指针函数不能返回局部变量的地址,因为函数调用结束后,局部变量占用的内存空间就被操作系统回收了

可以返回:

1.全局变量的地址

2.static关键字修饰的局部变量的地址

3.由参数传递过来的地址

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值