c语言------小漏洞

字符串问题:

在这里插入图片描述

# include<stdio.h>
# include<string.h>
int main(){
	char str[20];
	gets(str);
	str[11] = '\0'; 
	puts(str);
	char ss[20];
	ss[0] = 2;
	//ss = str;   数组不能这么操作 
}`

在这里插入图片描述

# include<stdio.h>
# include<string.h>
int main(){
	char str[3];
	gets(str);
	//str[11] = '\0'; 
	puts(str);
	char ss[20];
	ss[0] = 2;
	//ss = str;   数组不能这么操作 
}

在这里插入图片描述

# include<stdio.h>
# include<string.h>
int main(){
	char str[3];
	gets(str);
	//str[11] = '\0'; 
	str[0] = 4;  //吧输入的str[0]覆盖; 
	puts(str);
	printf("%c", str[1]);
	char ss[20];
	ss[0] = 2;
	//ss = str;   数组不能这么操作 
	
}

结构体:

(*p).name <=> p->name;
大括号内定义的变量都是局部变量,包括int main(){}.
当局部变量与全局变量发生冲突时(变量名一致),优先局部变量.
在这里插入图片描述

# include<stdio.h>
int a = 10;
int main() {
	int a = 20;
	printf("%d", a);
	return 0;
}

定义数组时,中括号中的不能是变量或者变量表达式,只能是常量或者常量表达式.

//C99标准中引用一个概念:变长数组
//支持数组创建的时候,用变量指定大小的,但是这个数组不能初始化
//但是VS2022不支持C99中的变长数组
# include<stdio.h>
int main() {
	int n = 10;
	int arr[n];     
	return 0;
}

在这里插入图片描述'\060’属于转义符号:
反斜杠后面明显是一个8进制数,将此八进制数转换成10进制后的值,这个值作为ascll值所对应的字符
switch语句中的关键字不包括break,break用于循环,而switch用于分支.
计算数组元素个数代码:

int arr[];
int n = sizeof(arr) / sizeof(arr[0]);
//不能用strlen,因为这是整形数组,不是字符型

数组传参:

问题:查找数组元素
// 主要看数组传参问题

# include<stdio.h>
int binary(int a[], int i, int sz) {//这里写a[10]和a[]一样的效果,解释在下.
	int left = 0;
	int right = sz - 1;
	int mid;
	while (left <= right) {
		mid = (left + right) / 2;
		if (i > a[mid]) {
			left = mid + 1;
		}
		else if (i < a[mid]) {
			right = mid - 1;
		}
		else return mid;//返回下标值
	}
	return -1;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz;
	int i = 7;//要查找的元素值
	sz = sizeof(arr) / sizeof(arr[0]);
	int ret = binary(arr, i, sz);
	if (-1 == ret) {
		printf("找不到了");
	}
	else printf("%d", ret);
	return 0;
}

在这里插入图片描述假如binary传参不传sc,而是在binary函数内计算sc时,代码及运行结果如下:

# include<stdio.h>
int binary(int a[], int i) {
	int left = 0;
	int sz = sizeof(a) / sizeof(a[0]);
	int right = sz - 1;
	int mid;
	while (left <= right) {
		mid = (left + right) / 2;
		if (i > a[mid]) {
			left = mid + 1;
		}
		else if (i < a[mid]) {
			right = mid - 1;
		}
		else return mid;//返回下标值
	}
	return -1;
}
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 7;//要查找的元素值
	int ret = binary(arr, i);
	if (-1 == ret) {
		printf("找不到了");
	}
	else printf("%d", ret);
	return 0;
}

在这里插入图片描述找不到了!!!
因为函数在传参,而参数是数组时,传进去的是数组首元素的地址,所以形参里面写int a[] 和 int a 都是一个效果,既然传进去的参数a只是一个地址,所以sizeof(a),64位机器计算sizeof(a)就是8,8/4=2,所以算出来的sc并不是实际数组大小,而是2.*
这也解释了为什么形参里面写int a[]和int* a效果都一样的原因.
那么又有了新疑问:在主函数中计算sizeof同样是讲a作为参数传入了sizeof()函数中,那么这里传入的是不是首元素地址呢?那肯定不是,所以这便是一个例外:sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节.除此之外,还有另一个例外:就是&数组名,这里的数组名表示的就是整个数组所有元素的地址,除这两种情况外,数组名表示的都是数组首元素地址
别听小王扯犊子了,刚知道sizeof其实根本不是一个函数,而是一个操作符,详细情况查看操作符那一个博客.
&arr[0] = arr !=&arr; &arr表示的是整个数组中所有元素的地址
&arr+1代表的地址是增加了一个数组大小后的地址
而arr+1代表的是数组中增加了一个元素后的地址,即第二个元素的地址.
还有一个小知识点,就是***形参是数组时,如果是一维数组,则中括号中的数字可以省略,但是如果是二维数组,则中括号中的行数可以省,但是列数不可以省.但是最好行数和列数都写,二维数组中形参也可以是指针,但是暂时不考虑,后期补充.***

函数不可以嵌套定义,但可以嵌套调用
函数的链式调用 : 将函数的返回值当作参数传入另一个函数中
先看看strcpy函数背后的隐藏秘密:
在这里插入图片描述可以看到编译器遇到strcpy后调用strcpy函数,并返回一个指针类型.
其实c语言中每一个语句都对应后台代码中的一个函数,printf也不例外.
所以以下代码就可以看作是函数的嵌套调用:

printf("%s", strcpy(arr1, arr2));

那么看到这里,再看下面代码:

printf("%d", printf("%d", printf("%d", 43)));

看到这里我时常嘲笑自己是个小垃圾,小王知道会输出什么吗?
错,输出的是4321.看解释:
在这里插入图片描述可以看到c语言编译器的后台代码给printf函数的返回值是一个整形数字,这个数字是
在这里插入图片描述小王你代码学不会,英语还看不懂,让你舍友保研去吧!
翻译 :返回值是打印在屏幕上的字符(数字)个数
首先最右边的printf函数输出了43,返回值位43的字符个数(43为两个数字,所以返回值为2)2,中间的printf函数输出最右边printf函数的返回值2,同时也返回了打印在屏幕上的字符个数(2的字符个数为1),所以最左边的printf函数返回值为1,所以最后的返回值为4321.

函数的声明:
告诉编译器有一个函数叫什么,参数是什么, 返回类型是什么,至于具体是不是存在,无关紧要(存在与否取决于定义).
出现在函数使用之前,满足先声明后使用
声明一般放在头文件中(# inlcude “函数的声明的文件名”),而定义一般写在源文件中, 也可以同时声明和定义,但是这样做必须要在main()函数之前定义.

导入静态库:
# pragma comment (lib, "静态库名称")

strlen 函数的返回值为无符号整形数.

循环是一种迭代.

常量:

常量分为字面常量, 长变量,宏定义的标识符常量和枚举常量
字面常量:

3.15 浮点型常量
10 整形常量
‘a’ 字符常量
“dknf” 字符串常量

const修饰的常变量:

const num = 10; //num就是常变量 ---具有常属性,不能被改变的属性
在定义数组时:
int num = 10;
int arr[num]   //系统会报错
const int num  = 10;
int arr[num]    //系统依然会报错

这个例子说明了常变量和常量不相同,其本质还是变量,只是赋予了常量的属性

#define 定义的标识符常量:
#define MAX 100000
枚举常量:
//可以一一列举的常量:

enum Sex
{
	//这种枚举类型的变量未来的可能取值
	//枚举常量
	MALE
	FEMALE
	SECRET
}
//上面那三个大写字母的量就是枚举常量,意思是当我定义一个枚举变量s,如:
enum Sex s = MALE;
//这个枚举变量的值只能是Sex里面的枚举常量
//当用printf函数依次输出上面三个枚举变量时,会输出0,1,2因为不指定MALE的值时,默认为0,给MALE赋值只能在enum中赋初值,加入赋初值3,此时依次输出3,4,5;

获得一个数二进制中有几个1有几个0的算法

有几个1:
算法1:

# define _CRT_SECURE_NO_WARNINGS 1
# include<stdio.h>
int fun1(int n) {
	int count = 0;
	while (n){
		count++;
		n = n & (n - 1);  //对于该算法的探究在电脑照片中详看
}
	return count;
}
int fun2(int n) {//(判断条件暂时未知)
	int count = 0;
	while (n) {
		count++;
		n = n | (n + 1);
	}
	return count;
}
int main() 
{
	int n;
	scanf("%d", &n);
	printf("%d\n", fun1(n));
	printf("%d\n", fun2(n));
	return 0;
}

算法2:

int fun1(int n) {
	int count = 0;
	/*while (n){
		count++;
		n = n & (n - 1);
}
	return count;*/
	while (n) {
		if (n % 2 == 1) {
			count++;
		}
		n /= 2;
	}
}

算法三:

int fun1(int n) {
	int count = 0;
	/*while (n){
		count++;
		n = n & (n - 1);
}
	return count;*/
	/*while (n) {
		if (n % 2 == 1) {
			count++;
		}
		n /= 2;
	}*/
	int i = 0;
	for (i = 0; i < 32; i++) {
		if ((n >> i) & 1 == 1) {
			count++;
		}
	}
	return count;
}

64位:

zu:标准的sizeof返回的无符号整形
如:printf("%zu", sizeof(char));

在这里插入图片描述32位:
在这里插入图片描述都一样
c语言规定:
sizeof(long)>=sizeof(int);

大括号内的是局部变量,大括号外是全局变量
要养成定义变量时就初始化的好习惯

变量的作用域与生命周期:

作用域:
变量在哪里可以使用哪里就是他的作用域
局部变量的作用域就是变量所在的局部范围(局部变量所在的大括号内)
全局变量的作用域是整个工程,用于同一工程的不同源文件中需要用extern声明来自外部的符号

int a;
extern int a;

生命周期:
局部变量:生命周期进入作用域开始,出作用域结束
全局变量:整个程序的生命周期
以下内容来自百度:

作用域与生命周期是两个完全不同的概念。在英文中,作用域用“scope”表示,生命周期则用“duration”表示。作用域是一个静态概念,只在编译源程序的时候用到。一个标识符的作用域指在源文件中该标识符能够独立地合法出现的区域。生命周期则是一个运行时(Runtime)概念,它是指一个变量在整个程序从载入到结束运行的过程中存在的时间周期。由于函数和数据类型是静态的概念,它们没有生命周期的说法,它们从编译、程序的运行到结束整个过程是一直存在的。
C++中作用域的级别由高到低,主要有文件域(全局作用域)、名字空间域、类域、函数作用域和代码块作用域,其中函数作用域和代码块作用域又统称为局部域。

注意c语言的注释不支持嵌套注释(/* */)
shift加方向键可以代替鼠标拖动,仅适用于打字和敲代码.

0 -------- 数值0
‘0’ ------字符0-------ascll值为48
‘\0’-------字符--------ascll值为0
EOF--------end of file ------文件的结束标志-----值为-1
在这里插入图片描述在这里插入图片描述

数组:

char arr[] = {‘a’, ‘b’, ‘c’}; == char arr[3] = {‘a’, ‘b’, ‘c’};
他们两个strlen(arr)后输出的都是随机值
但是arr[4] = {‘a’, ‘b’, ‘c’}
strlen(arr) 输出值为3;
因为’\0’的ascll值为0,所以会将0当作’\0’;

结构体

结构体定义同时也可以进行初始化:

struct Stu {
	char name[20];
	int age;
	char sex[10];
	char tele[12];
};
struct Stu s = { "zhgnaoen", 20, "nan", "13333333333" };

int ch = getchar()
getchar是要从键盘得到一个字符,为什么用char不用int?
个人理解原因有二:
第一本身getchar函数的返回值就是一个整形,以输入的数的ascll值为返回值.
第二当读取失败时,getchar的返回值时EOF(-1),-1是整形,占四个字节,所以为了使getchar函数读取字符是否成功,都能有返回值.

# include<stdio.h>
int main() {
	int ch = 0;
	while ((ch = getchar()) != EOF)
	putchar(ch);
	return 0;
}

在这里插入图片描述程序停止即想让getchar函数返回值为EOF,需要从键盘输入Ctrl+Z.
这里会自动换行的原因是在从键盘输入字符时,会输入一个回车,输入缓冲区里面就会有两个字符(其中一个是\n).
在这里插入图片描述在这里插入图片描述这样的代码适当的修改是可以用来清理缓冲区的
清理缓存区的应用:

# define _CRT_SECURE_NO_WARNINGS 
//调试时发现# define _CRT_SECURE_NO_WARNINGS 必须写在第一行,他很傲娇,不甘屈膝于# include<stdio.h>之下
# include<stdio.h>
int main() {
	char password[20] = { 0 };
	printf("请输入密码:");
	scanf("%s", password);
	printf("请确认密码(Y/N):");
	int ret = getchar();
	if ('Y' == ret)
		printf("Yes\n");
	else
		printf("No\n");
	return 0;
}

这行代码表面上可以自己在键盘上输入Y或者N来确认密码,但是scanf和上面的getchar一样中间都具有一个缓冲区,同样输入会车时,\n会储存到缓存区中,所以其实这里的getchar读取的是缓冲区中滞留的\n,发生下面的情况,因此这里需要清理缓冲区.
在这里插入图片描述修正后的代码:

# define _CRT_SECURE_NO_WARNINGS 
# include<stdio.h>
int main() {
	char password[20] = { 0 };
	printf("请输入密码:");
	scanf("%s", password);
	getchar();
	printf("请确认密码(Y/N):");
	int ret = getchar();
	if ('Y' == ret)
		printf("Yes\n");
	else
		printf("No\n");
	return 0;
}

在这里插入图片描述虽然解决了这种情况,但是其实这样的代码仍然有漏洞:
在这里插入图片描述当输入的密码带有空格的时候,确认密码时仍然有bug,原因是scanf接收缓冲区里面的内容时,遇到空格就会停止读取,而后面的getchar仅仅是读取了缓冲区里面的空格,第二个getchar会读取到a从而输出No,因此要想在第二个getchar读取字符前缓冲区为空,就需要如下的我代码改进:

# define _CRT_SECURE_NO_WARNINGS 
# include<stdio.h>
int main() {
	char password[20] = { 0 };
	printf("请输入密码:");
	scanf("%s", password);
	while (getchar() != '\n');
	printf("请确认密码(Y/N):");
	int ret = getchar();
	if ('Y' == ret)
		printf("Yes\n");
	else
		printf("No\n");
	return 0;
}

在这里插入图片描述并不是密码里不能有空格,而是当密码里有空格时,不能用scanf接收,需要用gets(),这里用scanf只是引出这种逻辑.

c语言for循环小括号里面的内容不要随便省略,否则会带来意想不到的后果,比如下面代码:

# define _CRT_SECURE_NO_WARNINGS 
# include<stdio.h>
int main() {
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++) {
		for (; j < 3; j++)
			printf("hehe\n");
	}
	return 0;
}

程序运行起来之后会发现上面的代码只打印出三个hehe,是因为i=0 的时候j循环了三次,j= 3时i++,这时j仍然等于3,由于第二个for循环中没有初始化语句,所以j等于3;

rand()函数获得的随机数是0到RAND_MAX(0x7fff = 32767)

go to语句:

go to语句不能跨函数跳转.
c 语言提供了可以随意滥用的goto语句和标记跳转的标号
从理论上goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码.
但是某些场合下goto语句还是用得着的,最常见的用法就是终止程序在某些深度嵌套的结构的处理过程
例如:一次跳出两层或者多层循环
size_t 是无符号int类型
memory ---------内存
memset 函数
函数传参时,只是将实参的值传给了形参,形参可以看作是实参的临时拷贝,但是由于实参和形参有各自的独立空间,所以在函数中修改形参的值时实参不会发生改变

二分查找时:为了使得left + right有意义,吧right+left写成left+(right-left)/2更好

程序里面如果要引入布尔类型,头文件需要包含# include<stdbool.h>
bool类型的值sizeof为一个字节.

定义函数时有返回值但是函数中没有写return的时候有的编译器会默认返回函数中最后一个指令的结果,如函数中最后一个指令如果是printf(“hehe\n”);则会返回5(因为有五个字符).
本质上main函数是有参数的
int main(int argc, char* argv[], char* envp[])
至于参数到底是什么意思这里先不做解释.因为牵扯的东西很复杂,这里见了之后晓得就好

库里的头文件用<>
自己定义的头文件用""

#pragma once:防止头文件被重复包含
%u打印无符号数

函数设计追求高聚合低耦合

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

With Order @!147

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值