当前位置: 首页 > article >正文

C语言学习笔记(1)

在学习前,需要有一定的C语言基础。不必很深入,只需要知道函数,头文件,指针,数组等的概念就可以,但并非0基础笔记。

由于写到后面,不好编辑了,决定分成多篇写,请按编号学习,或者直接看目录先。将会包括,c,c++,linunx_C系列

其他内容将放在入门到精通专栏里面

说明:该文章来自本人学习时的笔记,使用的编译器是Visual Studio,如有错误,可以在评论区或者私信我纠正,谢谢 

1.前提知识

1.1项目结构

1.2 visual Studio导出模板

1.3预处理

预处理指令:以#开头的

转移符:\r

#include <stdio.h>	//预处理

int main (void){	//C语言里面,标准必须加void表示不定形参,c++空括号不加表示void
	printf("hello world\r");	//  \反斜杠   /斜杆  \r转义序列
	return 0;
}

预处理:

预处理:把头文件复制到文件里面

# include<stdio.h>	//把头文件复制过去

宏定义:

# define N 5 //代码里面N会在预处理的时候被直接替换为5

宏函数

# define FOO(x)  (1+(x)*(x))  //在预处理的时候会直接把宏函数内容替换
							//整个表达式要用括号括起来,第一个左括号要贴紧宏函数,参数要用括号括起来
							//调用宏函数没有调用函数的开销,可以避免简单重复的函数调用
							//提供了一定的宏编程

多行宏函数:用\换行

#define SWAP(a,b){\
		int t=a;\
		a=b;\
		b=t;\
	}

1.4编译和链接

预处理、编译、汇编、链接

1.5进程的虚拟内存空间

内核  	//和内核交互
栈		//管理函数调用
()
堆		//存放动态数据
数据段	   //
代码段

1.6 注释

只在必要的时候写注释

2.变量

三要素:变量名、类型、值

2.1标识符

2.2关键字

在C语言里面有特殊含义的标识符

3.输入输出模型

3类资源:CPU、内存、外存

3.1 printf 格式化输出

%m.px    //最少占有m个位置,在前面补空格   |   最少显示p个数字,不足的前面补0    |  x表示输出格式
%-m.px	//在后面补空格

3.2 scanf 格式化输入

%d	//忽略前置空白字符,匹配一个十进制整数
%f  //忽略前置空白字符,匹配一个浮点数
格式串  //精确匹配
空白字符 //匹配任意个空白字符

3.3scanf忽略空白字符

scanf("%c %c");//跳过空白字符匹配下一个非空白字符

3.4 putchar与getchar的使用 

char c='A';
putchar(c);//输出c的字符
c=getchar();//用于输入一个字符


//惯用法
while(getchar=!='\n'){//跳过换行符后的剩余的字符
};

4.一些基本算法

4.1不分类
4.1.1求最大公约数

#求最大公约数
int gcd(int a, int b) {
	if (a < b) {
		SWAP(a, b);
	}
	while (b != 0) {
		int r = a % b;
		a = b;
		b = r;
	}
	return a;
}

4.1.1判断是否为素数

函数的功能应该越单一越好,既然要判断一个数是不是素数,实现的功能只需要返回是或者不是,不要加其他多余的东西提高代码的复用率。

bool is_Prime(int n){
	int rt = sqrt(n);
	for(int i=2;i<=rt;i++){
		if(n%i==0){
			return false;
		}
	}
	return true;
}

4.2位运算

4.2.1交换两个数的值(不用中间变量)

可以这么做,但不建议,代码可读性很差

//交换方法1:可能出现溢出问题
a=a+b;
b=a-b;
a=a-b;
//交换方法2:可读性很差
a=a^b;
b=a^b;
a=a^b;

 4.2.2找数组中唯一的单独的一个数

Q:数组中有一个元素出现过一次,其余元素出现过两次,找这个单独的元素

int findSingleNum(int nums[],int n){
	int singleNum=0;
	for(int i=0;i<n;i++){
		singleNum^=nums[i];//相同的数异或两次同一个数会变回原来的,
                            //唯一的单独数异或以后会存在singleNum中
	}
	return singleNum;
}

4.2.3找两个元素

Q:数组中有两个元素出现过一次,其余两个元素都出现过两次,找这两个单独出现的元素,可以按任意顺序返回答案

思路:

先求xor=a^b

xor正数与xor负数的补码表示上,在最低有效位上相差1

lsb即xor^(-xor)的值为xor的最低有效位为1,其他位为0的结果

a和b在这一位上一个为1一个为0

分类以后回归到2.2的题目

void findTwoNums(int nums[], int n) {
	int Xor = 0;
	for (int i = 0; i < n; i++) {
		Xor ^= nums[i];//xor=a^b
	}
	int lsb = Xor ^ (-Xor); //a和b在这一位上不同
	//根据这一位将所有元素分类
	int a = 0, b = 0;
	for (int i = 0; i < n; i++) {
		if (nums[i]&lsb) {
			a ^= nums[i];//相同的数一定会两次都和a或b相与,结果不变
		} else {
			b^=nums[i];
		}
	}
	printf("两个数分别是 %d 和 %d \n",a,b);
}

5.编码

无符号整数(二进制编码)

有符号整数(补码)

Q:为什么用补码做有符号整数编码?

A:因为可以用加法达到减法的效果。

 5.1 ASCII字符编码

5.1.1编码表

5.1.2大小写字母转换

int c=0x70;//P的编码
int tolower(int c);//把P转化为p
int toupper(int c);//把P转化为p

5.2字符转义序列

5.3数字转义序列

八进制(最多3位数字):‘\000’

十六进制转义序列:'\x000'

查看手册网址:C reference - cppreference.com

6.类型转换

6.1隐式转换

不同类型运算隐式转换,编译器会帮你自动转化为下一个等级

6.1.1整数提升

int -> long  ->  long long  ->  float  ->  double  ->  long double

6.1.2同一整数等级的转换

signed  ->  unsigned

提示:千万不要把无符号整数与有符号整数混合运算

6.2显示转换(强制转化)

int a=10;
float f;
f=(float)a;

6.3定义别名

Q的作用等价于int,重命名的目的是提高代码可读性

typedef的可移植性强,可以在前面定义别名的使用,只需要改变一次

比如int在桌面端占用4个字节,在嵌入式的时候可能就只有两个字节,只需要把重命名的前面的类型改变,比如嵌入式中int占用2字节,long占用4字节,只需要把int改为long就可以恢复桌面端占用4字节int的效果。

typedef int Q;//把int重命名为Q
Q a=0;//a是int类型

6.4sizeof计算长度

计算类型的时候sizeof时需要加上括号,本质上sizeof不是函数而是运算符。直接每次都加括号就好

int len=sizeof(int);//可以
printf("%d",len);

7.运算符

7.1赋值表达式

发生的过程为

i=3.14;//i被赋值为3

f=i;//f被赋值为3.00

int i;
float f;
f=i=3.14;//f的值为3.00,i的值为3

7.2自增

i++与++i

不必理会先自增还是后自增

把其理会为i=1,执行完语句以后副作用是会自增1

7.3位运算

7.3.1移位运算符

左移 <<,右移>>

在没有溢出的前提下,相当于左乘2,右除2

最好只对无符号整数进行移位运算

7.3.2按位运算符

~按位取反

&按位与,全1才1

|按位或,有1就1

^按位异或,同0异1

7.3.3位运算的性质

1.只能用于整数运算

2.有交换律和结合率

3.除了取反运算符都可以和等号结合变成复合运算符

        i^=j等价于 i=i^1

7.4算术运算符

8.调试

8.1三种错误

1.编译错误:

C***:语法错误

2.链接错误:

LNK***:缺少头文件/函数名写错了

3.运行时错误:

运行时错误往往是逻辑错误,也就是需要调试,也就是常说的bug

8.2调试方式

1.打断点

2.调试在这边,左边的红色圆点是断点

3.开始调试以后,会直接运行到第一个断点,并把断点的上一行运行完,断点这行还没有运行

4.继续运行

点击继续会运行到下一个断点

5.逐语句运行

顾名思义,就是一条一条语句往下执行,点一下运行一条,遇到函数会跳到被调函数里面

6.逐过程运行

遇到函数会直接完成函数的过程直到下一个断点,不会跳到函数里面一行一行运行,而是直接完成函数的整个过程。

点击逐过程会直接完成Foo函数的运行,到下一个断点

7.跳出

在调试的过程中,发现函数没有问题,直接完成函数的运行跳出被调函数,回到主调函数里面

9.语句

9.1语句分类

表达式语句

选择语句:if     switch

循环语句 :while     do...while    for

跳转语句:continue ,break , goto  ,return

空语句:     ;

复合语句:{   }

9.2 switch语句

解决的问题:效率低,可读性差,运行效率比if-else高

限制条件:

1.switch后面的表达式是必须是整型(char,枚举)

2.switch后面的表达式与标签case是用==比较的

switch (a) {
	case 1:
		//TODO
		break;
	case 2:
		//TODO
		break;
	default:
		//TODO
		break;
}

注意事项:

1.可以多个case公用一组语句

2.会出现case穿透现象(在利用的时候,在下面要加上break through的注释,表示特意要让他穿透的)

switch (a) {
	case 1:case 2://1和2都运行下面这个TODO
		//TODO
		break;
	default:
		//TODO
		break;
}

10.数组

数组的单元叫元素,结构体的单元叫成员

10.1数组的内存模型

连续的一片的内存空间,有随机访问的性质。

数组的索引不是代表第几个元素,而是代表与第一个元素的偏移量

刻板印象:数组效率>链表效率

        1.空间利用率

        2.计算机按块读,数组的空间局部性好

  

10.2数组的定义

int arr[4];//声明一个数组
//变量名为arr
//类型为int[4]

10.3常数

const

11.随机

srand(time(null)):随机种子,一般只需要生成一次,一般这个数非常大,往往需要取余

rand:随机生成一个伪随机数

time:1970.1.1.00:00到现在的时间,要包括time.h

12.函数

12.1与数学函数的比较

数学:有返回值,没有副作用

C语言:可以没有返回值,可以有副作用

12.2准则

1.函数的功能应该越单一越好,函数的实现应该越高效越好

2.函数是c语言的基本构建组件,c语言的本质就是函数之间的调用,做开发的时候,要聚焦于函数,而不是细节

12.3函数相关概念

函数定义:void foo(int a){...}

函数声明:void foo(int a);

函数调用:foo(3);

函数指针:foo     ,&foo

12.4值传递

栈帧中存入函数的参数,局部变量,返回地址

在值传递的时候,被调函数是无法修改主调函数的值,但是可以用指针解决

但不是所有值传递的时候,被调函数都无法修改主调函数的值,如果传递的值是数组,就可以修改

数组在参数传递的时候,会自我退化为一个指针,指向数组的第一个元素的指针,传递的第一个元素的地址,操作的还是原来的那个数组。

数组作为参数传递的时候,必须把长度传递过来,也不能使用SIZE宏来计算传递数组的长度。

Q:为什么说是数组退化为指针?

A:因为数组退化为指针以后,数组丢失了长度信息

Q:为什么这样设计?

A:1.避免大量数据的复制

       2.被调函数可以操作主调函数的数组

       3.让函数的调用更加灵活,比如传递一个长度为10的数组,直接对前面5个元素清零

12.5存储期限

存储期限:在运行时起作用,表示这个变量绑定的值在可以被引用的时间长度

静态存储期限:内存中,数据段与代码段的部分,类似c++的生命周期,在进程启动时开始,在进程退出时消亡

自动存储期限:栈帧入栈时开始,栈帧出栈时消亡

动态存储期限:由程序员自己管理,malloc开始,free消亡

12.6局部变量与外部变量

局部变量:定义在函数里面的变量,只能在函数里面使用的变量

        作用域:变量可以被引用的范围,或者说是在{...}块内的范围,(在运行时起作用)

        块:简单说就是大括号{...}里面的全部叫一个块

        存储期限:默认为自动存储期限(在栈里面),加上static会变成静态存储期限(在数据段里面),加了static初始化只执行一次。

外部变量:定义在函数外部的变量,也叫全局变量,但并非全局都可以使用。、

                作用域:在外部变量的定义开始到这个文件的末尾,也可以叫文件作用域

                存储周期:静态存储期限

静态存储期限的局部变量和外部变量的区别是作用域不同

Q:静态存储期限的局部变量有什么用?

A:存储上一次函数调用的状态

 eg:使用静态存储期限的局部变量打印一个斐波那契

long long next_fib() {
	static long long a = 0;
	static long long b = 1;
	long long t = a + b;
	a = b;
	b = t;
	return a;
}
int main() {

	printf("next_fib() = %lld\n", next_fib());
	printf("next_fib() = %lld\n", next_fib());
	printf("next_fib() = %lld\n", next_fib());
	return 0;
}

12.7递归 recursion

12.7.1递归

递:将大问题分解为若干个子问题

       子问题和大问题的求解方式一样,只是问题的规模不一样

归:将子问题合并为大问题的解

以斐波那契为例子,递归是指数增长级别的,求解方式的效率很低,会纯在大量重复的计算

long long fib(int n){
	if (n==0||n==1){
		return n;
	}
	return fib(n-1)+fib(n-2);
}

12.7.2动态规划

具有递归结构的问题不一定要用递归求解,可能纯在大量重复计算的问题,不一定要自下而上的求解,可以使用动态规划的方式去优化避免重复计算

继续以斐波那契为例子,使用动态规划

long long fib_dp(int n){
	long long a=0;
	long long b=1;
	//循环不变式:每次进入循环体之前都成立的条件
	for (int i =2;i<=n;i++){
		long long t=a+b;
		a=b;
		b=t;
	}
	return b;
}

12.7.3循环不变式

循环不变式:每次进入循环体之前都成立的条件,要找到退出点,可以一直维持到循环语句的结束

可以用小的规模去计算得到正确的结果

控制表达式:上面i<=n那句叫控制表达式,要关注要不要写等号

12.7.4递归关键

递归公式:只考虑这一层与下一层的条件(使用数学归纳法)

边界条件:最后得到答案的条件

这个只能靠自己多刷题。

12.7.5要不要用递归——递归两问

1.找到问题的递归结构

2.要不要使用递归结构:存在重复结构(不用),问题缩减幅度很小(不用)

999.常见面试题

999.1题目清单

  1. 位运算
    1. 如何判断一个数是奇数
    2. 为什么数组的索引一般是从0开始的?

999.2位运算

1.如何判断一个数是奇数

错误做法:

        i%2==1

        当i为负数的时候,i的结果为-1,C语言的%和数学的mod是不一样的

        C语言%运算的时候,余数的符号与被除数是一致的

可行做法:

        i%2!=0

        提示:取余,乘法,除法的运算符都是比较麻烦的,计算机操作的数都是二进制

 正确做法:

        i & 0x1==1;

        建议使用16进制数,使用16进制表示关注的是二进制,而不是数本身

2.为什么数组的索引一般是从0开始的?

数组元素的地址计算公式为:i_addr  = base_adddr + sizeof(elem_type)*(i-1)

数组的索引不是代表第几个元素,而是代表与第一个元素的偏移量


http://www.kler.cn/a/454302.html

相关文章:

  • 可灵1.6正式上线,图生视频再创新视界
  • pdf有密码,如何实现pdf转换word?
  • 使用vue3搭建前端模拟增删改查
  • GitLab 服务变更提醒:中国大陆、澳门和香港用户停止提供服务(GitLab 服务停止)
  • 7. petalinux 根文件系统配置(package group)
  • 浏览器工作原理与实践-12|栈空间和堆空间:数据是如何存储的
  • 游戏引擎学习第62天
  • Maven核心概念总结
  • Blender高效优化工作流程快捷小功能插件 Haggis Tools V1.1.5
  • jvm排查问题-实践追踪问题 与思路--堆内堆外内存泄漏排查方针
  • HarmonyOS NEXT 实战之元服务:静态案例效果---咖啡制作实况窗
  • css
  • 随时随地编码,高效算法学习工具—E时代IDE
  • PDF书籍《手写调用链监控APM系统-Java版》第10章 插件与链路的结合:SpringBoot环境插件获取应用名
  • Uniapp 微信小程序检测新版本并更新
  • 数据分析的常见问题及解决方案
  • 安全合规遇 AI 强援:深度驱动行业发展新引擎 | 倍孜网络CEO聂子尧出席ICT深度观察报告会!
  • C++-----------映射
  • Java Spring Boot 项目中嵌入前端静态资源:完整教程与实战案例
  • 模板方法、观察者模式、策略模式
  • Security知识点分享之高级安全安装虚拟机
  • 电商数据的安全与隐私保护:API接口的角色
  • 开源代码寻找平台总结
  • 【数据结构】【线性表】栈在算术表达式中的应用
  • McDonald‘s Event-Driven Architecture 麦当劳事件驱动架构
  • 分布式 IO 模块助力冲压机械臂产线实现智能控制