C语言学习Part1(1-1000行代码)

时间:2024-01-22 18:02:02
什么是C语言?

计算机语言

二进制语言-> 汇编语言-> B语言-> C语言/C++语言

C语言是一门结构化的程序设计语言
	1-顺序结构
	2-分支结构--if/switch
		(else与离他最近的if匹配!)
		(switch表达式必须是整型,case表达式是整型常量表达式)
		(switch case语句中可以出现if)
		(switch case语句允许嵌套)
	3-循环结构--while/for/do while
    
 一个{}就是一个代码块
如何学好C语言?

学好编程,不只是C语言:

 必须学好语言、算法、数据结构(DS)、系统(Linux,Windows)、

 网络(网络基础、网络编程)


头文件
#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>
// 包含一个头文件
// std-> 标准 standard input output

// printf-> print function
C语言中的格式


%c-->字符格式
%p-->地址
%x-->十六进制
%f-->5位小数位
%lf-->6位小数位
内存上:(字节数)


char(1)  short(2)  int(4)  long(4/8)
long long(8)  float(4)  double(8)
  
C语言规定:
sizeof(long) >= sizeof(int) 与平台有关


计算机中的单位
bit-->比特位  只能存一个二进制位(0/1)
byte-->字节  1字节=8比特位
kb    1kb=1024byte
mb    1mb=1024kb
gb、tb、pb


范围:


int--> 4字节(0~2^32 -1)--32个0~32个1
short int--> 2字节(0~2^16 -1)--16个0~16个1

float weight = 96.5f;
//默认是double,用f避免精度丢失
C语言规定:所有变量定义要在当前代码块的最前面
//全局变量的生命周期即整个程序的周期

const int num = 4;
// const--常属性
// num--const修饰的常变量
// 本质还是变量,但具有常属性

//放在main()函数内的变量是局部变量

#define定义标识符常量
#define max 10
int main() {
int arr[max] = { 0 };
//用法正确
}

//枚举常量

enum Sex
{
	MALE,   //0
	FEMALE, //1
	SECRET  //2
};
int main()
{
	enum Sex s = FEMALE;
	printf("%d\n", MALE);
	printf("%d\n", FEMALE);//序号问题
	printf("%d\n", s);
	//MALE = 6;(x)
}

字符串-->双引号

//省略头文件
#include<string.h>
int main()
{
	char arr1[] = "abc";
	char arr2[] = { 'a','b','c' };
	//char arr2[5] = { 'a','b','c' };//就好了
	char arr3[] = { 'a','b','c',0 };
	printf("%s\n", arr1);
	printf("%s\n", arr2);//没有结束标志,会出现乱码
	printf("%s\n", arr3);
	int i = 0;
	int len = sizeof(arr2) / sizeof(arr2[0]);
	for(i=0;i<len;i++)
		printf("%c ", arr2[i]);
	printf("\n");
	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));//随机值,找'\0'
}
#include<string.h>
int main()
{
	char arr1[] = "abc";
	char arr2[] = { 'a','b','c' };
	char arr3[] = { 'a','b','c',0 };

	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);

	printf("%d\n", strlen(arr1));
	printf("%d\n", strlen(arr2));
	printf("%d\n", strlen(arr3));

	return 0;

}
转义字符
//转义字符
#include<string.h>
int main()
{
	printf("字符a:%c\12", 'a');
	printf("%d\n", '\A');
	printf("单引号:%c\n", '\'');

	//换行符即可以写作\n,
	//也可以写作\012(\12),           --->八进制
	//还可以记作\xa(\x0a或\x00a);    --->十六进制

	printf("\141\142\143\144\145\xa");
	//八进制的141实际数值为十进制的97,即对应了字母a的ascii码值

	//\xddd   ddd表示一个16进制数
	printf("1\xa\x30\xa");
	//十六进制的30的实际数值是十进制的48,即对应了字符0的ascii码值

	//而十六进制的\xa(a是十六进制的10)的实际数值是十进制的10,对应换行符

	printf("反斜杠:%c\n", '\\');

	printf("八进制数字:%c\n", '\130');
	//\ddd-->ddd表示1~3个八进制数字:\130
	//1*64+3*8=88-->X

	printf("长度:%d\n", strlen("c:\test\32\test.c"));//13

	printf("八进制32的ASCII码值对应:%c\n", '\32');
	printf("八进制32的十进制数字:%d\n", '\32');//26
	//\32--32是2个八进制数字
	//32作为八进制代表的那个十进制数字,作为ACSCII码值对应的字符是:
			//32->十进制26;ASCII码值对应“——>”
	printf("十六进制的61对应字符:%c\n", '\x61');//十六进制,对应a

	printf("八进制的60对应:%c\n", '\60');//字符0--八进制\60--十进制48
	printf("八进制的60对应:%d\n", '\60');
	return 0;
}

C语言学习Part1(1-1000行代码)_八进制

操作符
移位操作符:(2进制位)
 <<左移;>>右移
位操作符:(2进制位)
 &按位与		||按位或		^按位异或
复合赋值符:+=  -=  &=  >> ……
单目操作符:(一个操作数)
 &  !  ++ ……
双目操作符
关系运算符:
 >  <  >=  <=  !=  == ……
逻辑运算符:
 &&  ||
条件运算符:
 exp1 ?exp2: exp3--->max = (a > b? a : b)
   
转义字符\0的ASCII码值是0

EOF(end of file)文件结束标志-1

~  按位取反(2进制位)
~  按位取反(2进制位)
int main() {
	int a = 0;//4字节,32bit位,32个0
	int b = ~a;//32个1-->-1
	printf(" %d\n", b);//-1

	/*
  负数在内存中存储的时候存的是二进制的补码,输出要换成原码
	补码-1->反码->符号位不变,其余按位取反->原码
	11111111_11111111_11111111_11111111(32个)
	11111111_11111111_11111111_11111110(-1)
	10000000_00000000_00000000_00000001(取反)-->-1
 
 原码——反码——补码:
		只要是整数,内存中存储的都是二进制的补码
 整数三码相同;
	*/
}
//自增操作符
int main()
{
	int a = 10;
	int b = a++;//后置++,先用后加,b = 10;a = 11;
	printf("a = %d, b = %d\n", a, b);
	int c = ++a;//前置++,先加后用,c = 11;a = 11;
	printf("a = %d c = %d\n", a, c);
}
关键字
自动变量(一般省略):auto int a = 10;

extern ——> 引入外部符号
	extern int g_val;//引入别的.c文件的变量值(要求两文件的后缀名相同)
	extern int Add(int, int);//声明外部函数

register ——> 寄存器关键字
 计算机存储数据可存在:
		寄存器--高速缓存(70兆)--内存(4G/8G/16G)--硬盘
 CPU-->*处理器
		当CPU速度越来越快时,与内存速度已远远不匹配,就让内存把数据经高速缓存放到寄存器中,
		让CPU从寄存器拿数据,若拿不到,再向下找
	register int a = 10;
	//建议把a定义成寄存器变量,具体执行还需编译器自己判断

int == signed int//定义有符号数
unsigned int num = -1;//定义无符号数,num = 1

union ——> 联合体/共用体

typedef ——> 类型定义/类型重定义
	typedef unsigned int u_int;
	u_int a = 10;//起别名

static ——> 静态变量
	1>static修饰局部变量a,则a的生命周期变长(a = 10,就一直等于10)
	2>static修饰全局变量x,则改变了x的作用域,让静态全局变量只能在当前源文件中使用
	3>static修饰函数,则改变了函数的链接属性(普通函数具有外部链接属性),变成了内部链接属性

#define ——> 定义常量和宏
	#define PI 3.14;
	#define MAX(X, Y) (X> Y? X: Y)
指针
地址-->空间
如何产生地址?
	以32位为例:
		32位指有32根地址线/数据线,每根有正(1)负(0)电之分,
		就会产生2^32个地址和编号
		以一个字节为单位划分一个内存空间
	
int main()
{
	int a = 10;//4个字节
	int* p = &a;//取地址,p是存放地址的变量
	printf("%p\n", p);
	printf("%p\n", &a);//二者等价
	*p = 20;//改变原来变量a的值,*p就是a
	//*p ——> 解引用操作符
	printf("%d\n", a);
}

指针变量的大小?(只与平台有关)
	在32的平台上,1个32位bit位用4字节就够了(32个bit位存放32个0/1)
	在64的平台上,64bit位=8字节(64个bit位存放64个0/1)

int main()
{
	printf("%d\n", sizeof(int*));
	printf("%d\n", sizeof(char*));
	printf("%d\n", sizeof(double*));
	printf("%d\n", sizeof(int*[5]));

	printf("%d\n", sizeof(long*));
}

数组和指针的关系--数组名本身就是指针!
结构体
结构体struct
//处理复杂对象,是用户自己创建的一种类型

#include<string.h>
//类型创建在main函数之外
struct Book {
	char name[20];
	short price;
};
int main() {
	//创建变量
	struct Book b1 = { "C语言",55 };
	//打印
	printf("书名:%s\n", b1.name);
	printf("价格:%d\n", b1.price);
	//修改
	b1.price = 40;
	//结构体指针
	struct Book* pb = &b1;
	printf("%s\n", (*pb).name);
	printf("%s\n", pb->name);
		//操作符.  -->结构体变量.成员   b1.name/(*pb).name
		//操作符-> -->结构体指针->成员  pb->name
		
	//修改书名
		//由于name是数组,与price(变量)不同,
		//不可用b1.name = "C++"修改,只能用拷贝strcpy	
		//string copy——>字符串拷贝——>库函数<string.h>
	strcpy(b1.name, "C++");
	printf("%s\n", pb->name);

}

C语言是一门结构化的程序设计语言

1-顺序结构
2-分支结构--if/switch
(else与离他最近的if匹配!)
(switch表达式必须是整型,case表达式是整型常量表达式)
(switch case语句中可以出现if)
(switch case语句允许嵌套)
3-循环结构--while/for/do while
continue-->结束本次循环,直接进行下次循环
break-->跳出循环,永久终止循环
一个{}就是一个代码块
getchar()
int main() {
	int ch = 0;
	while ((ch = getchar()) != EOF)
		putchar(ch);

	return 0;
	//Ctrl + Z-->退出循环
	//EOF--end of file-->文件结束标志
}
switch-case结构
switch(整形表达式c)-->式中c可以是int,long char(ASCII),但不能是float
switch(整型表达式)
		{ 语句项; }
		case 整形表达式:
			语句;
			break;//实现分支
getchar()作用2——清理缓冲区
int main()
{
	
	int ret = 0;
	char password[20] = { 0 };
	scanf("%s", password);
	//由于数组名本身就是指针,不需要带&

	getchar();
	//用于清除回车换行'\n',清除缓冲区,('\n'的ASCII是10)

	/*1.从缓冲区读走一个字符,相当于清除缓冲区

	2.前面的scanf()在读取输入时会在缓冲区中留下一个字符'\n'(输入完s[i]的值后按回车键所致),
	所以如果不在此加一个getchar()把这个回车符取走的话,gets()就不会等待从键盘键入字符,而
	是会直接取走这个“无用的”回车符,从而导致读取有误*/

	printf("请确认(Y/N):>");
	ret = getchar();//接收Y/N
	if (ret == 'Y')
		printf("确认成功!\n");
	else
		printf("确认失败!\n");//ret = 10

	return 0;
}
int main()
{
	int ch = 0;
	while ((ch = getchar()) != EOF)
		putchar(ch);


	return 0;
}
//密码是123456 abc
int main()
{
	int ret = 0;
	char password[20] = { 0 };

	printf("请输入密码:>");

	//scanf("%s", password);
	//由于数组名本身就是指针,不需要带&

	//当输入123456 abcd时,scanf读取123456,缓存区剩 _abc\n
	//如何清除这么多符号?
	int ch = 0;
	int i = 0;
	while ((ch = getchar()) != '\n')
	{
		password[i] = ch;
		i++;
	}
	//空语句,先读取在判断,\n可以读进去
	//可读取1,2,3,_,4,5,6

	//getchar();
	//用于清除回车换行'\n',清除缓冲区,('\n'的ASCII是10)

	/*getchar每次只能读取一个字符.
	如果需要取消'\n'的影响,可以用getchar();来清除,
	这里getchar()只是取得了'\n'但是并没有赋给任何字符变量,所以不会有影响,
	相当于清除了这个字符.*/

	printf("%s\xa", password);
	putchar(ch);//是个换行,宝

	printf("请确认(Y/N):>");
	ret = getchar();//接收Y/N

	if (ret == 'Y')
		printf("确认成功!\n");
	else
		printf("确认失败!\n");//ret = 10

	return 0;

}
/*
	骐骥一跃,
	不能十步。
	驽马十驾,
	功在不舍。
*/
getchar()挑选数字
//字符'0'-->(ASCII)48
int main()
{
	int ch = 0;
	while ((ch = getchar()) != EOF)
	{
		if ((ch < '0') || (ch > '9'))
			continue;
		putchar(ch);
		
		//只会输出数字字符
		/*
		输入14
		循环两次,第一次判断1,第二次判断4
		*/
	}

	return 0;
}
for循环可以把初始化、判断条件、变量调整放在一起
使用for循环的建议:
1.不可以在for循环体内修改循环变量,防止for循环失去控制
	for(...)
	{
		if(i = 5) {}
		赋值语句,改变了循环变量
	}
2.建议for循环的循环控制变量的取值采取"前闭后开区间"的写法
	for(i = 0; i < 10; i++)
	[0, 10)
	10次循环,10次打印,10个元素

for语句的省略:
for循环的初始化、调整和判断都可以省略,但是:
	1.for的判断部分如果被省略,那么判断条件恒为真,死循环
	2.不建议随意省略!
//错误省略
int main()
{
	int i = 0;
	int j = 0;
	for (; i < 10; i++) {
		for (; j < 10; j++) {
			//当j = 10时,一直等于10,不打印了就
			printf("haha\n");
		}
	}
	return 0;
}
//e.g.求阶乘之和
int main()
{
	int i = 0;
	int sum = 0;
	int len = 0;
	int ret = 1;
	scanf("%d", &len);

	for (i = 1; i <= len; i++)
	{
		ret *= i;
		sum += ret;
	}	
	printf("%d\n", sum);
	return 0;
}
//e.g.从两边向中间覆盖打印
//##############
// 逐步覆盖
//hello C world!

#include<windows.h>
int main()
{
	char arr1[] = "##############";
	char arr2[] = "hello C world!";

	//左右下标
	int left = 0;
	int right = sizeof(arr1) / sizeof(arr1[0]) - 2;
	//[1,2,3,'\0']
	//[0 1 2   3 ]下标4-2=2

	//int right = strlen(arr1) - 1;

	//char arr[] = "abcd";
	//int sz = sizeof(arr) / sizeof(arr[0]);
	//printf("%d\n", sz);//5(自带'\0')

	while (left <= right)
	{
		arr1[left] = arr2[left];
		arr1[right] = arr2[right];
		left++;
		right--;
		printf("%s\n", arr1);
		
		Sleep(1000);//休息1s
		system("cls");//系统函数,清空屏幕,达到覆盖作用
	}
  
	printf("%s\n", arr1);
	return 0;
}
折半查找算法/二分查找算法
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int k = 7;//要查找的数字
	int sz = sizeof(arr) / sizeof(arr[0]);
	//元素个数
	int left = 0;
	int right = sz - 1;
	//下标 == 元素个数 - 1
	while (left <= right)
	{
		int mid = (left + right) / 2;
		if (arr[mid] > k)
			right = mid - 1;
		else if (arr[mid] < k)		
			left = mid + 1;
		else
		{
			printf("找到了,下标是:%d\n", mid);
			break;
		}
		
	}
	if (left > right)
	{
		printf("找不到了!");
	}

	return 0;
}
//e.g.输入密码,三次机会
#include<string.h>

int main()
{
	char pwd[20] = "0";
	int i = 0;

	for (i = 0; i < 3; i++)
	{
		printf("Please input your pwd:>");
		scanf("%s", pwd);

		if (strcmp(pwd, "123456abc") == 0)
		{
			printf("Entered successful!\n");
			break;
		}
		else
		{
			printf("The pwd you entered is incorrect.Please try again.\n");
			printf("You have %d more chances to enter.\n", 2 - i);
		}
	}
	if (i == 3)
		printf("The pwd is incorrect for 3 times,exit the program.\n");

	return 0;
}
求最大公约数——辗转相除法

辗转相除法是求最大公约数的一种方法。

它的具体做法是:

用较小数除较大数,再用出现的余数(第一余数)去除除数,

再用出现的余数(第二余数)去除第一余数,如此反复,直到最后余数是0为止。

如果是求两个数的最大公约数,那么最后的除数就是这两个数的最大公约数。


更相减损术
/可半者半之/

原理:

假设有两个数161和63,我们要求这两个数的最大公因数,不妨假定这个最大公因数为m,

我们可以将较大的数161看成63+98,63与98的和161可以被m整除,其中63也可以被m整除,自然98可以被m整除;

所以这个问题就转换为求98和63的最大公因数m(和上面m相等)

将98看成63+35,其中63可以被m整除,和98也能被m整除,故35也可以被m整除;

所以问题进一步转换为求35和63的最大公因数m(和上面m相等)

同理转换为求 (63-35)=>28和35 的最大公因数

然后转换为求28和7的最大公因数

…(一直减呀减)

后来转换为求7和7的最大公因数

最后转换为求7和0的最大公因数

输出第一个数字即可;这就是相减损术的原理

我们发现求28和7的最大公约数,一直减7,一直减7…减到不能减为止。这个不断减7的过程就是除7求余数(即%7)

第一步:任意给定两个正整数;判断它们是否都是偶数。若是,则用2约简;若不是则执行第二步。

第二步以较大的数减较小的数,接着把所得的差与较小的数比较,并以大数减小数。继续这个操作,直到所得的减数和差相等为止。

则第一步中约掉的若干个2的积与第二步中等数的乘积就是所求的最大公约数。

其中所说的“等数”,就是公约数。求“等数”的办法是“更相减损”法。

//e.g.更相减损法

int main()
{
	int a = 0;
	int b = 0;
	printf("请输入两个数:>");
	scanf("%d%d", &a, &b);

	int num = 0;
	int t = 0;

	//两个数相等,gcd是本身
	if (a == b)
		printf("gcd = %d", a);
	else
	{
		//保证a > b
		if (b > a)
		{
			int temp = a;
			a = b;
			b = temp;
		}

		//当两个数都是偶数时,要先➗2,直到其中一个不是偶数
		while (a % 2 == 0 && b % 2 == 0)
		{
			a /= 2;
			b /= 2;
			num++;
		}

		//核心算法:大数减小数,一直到减数与差相等为止
		while (a != b)
		{
			t = a - b;
			if (t > b)
			{
				a = t;
				b = b;
			}
			else
			{
				a = b;
				b = t;
			}
		}

	/*	while (a != b)
		{
			if (a > b)
				a -= b;
			else b -= a;
		}*/

	}
	if (num != 0)
		printf("gcd = %d\n", t * 2 * num);
	else
		printf("gcd = %d\n", t);

	return 0;
}
//e.g.求最大公约数
//辗转相除法
//1.m % n
//2.if(m % n != 0)
//		r = n; n = m;

// a=24			a=18
// b=18			b=6
// r=24%18=6	r=18%6=0

//a=33     a=12   a=9
//b=12     b=9    b=3
//r=9      r=3    r=0

int main()
{
	int a = 24;
	int b = 18;

	int r = 0;

	//while ((a % b) != 0)
	//{
	//	r = a % b;
	//	a = b;
	//	b = r;
	//}

	//简写版
	while (r = (a % b))
	{
		//r = a % b;
		a = b;
		b = r;
	}
	//printf("%d和%d的最大公约数为:%d\n", a, b, b);
	printf("最大公约数为:%d\n", b);
	return 0;
}