C语言学习
一.初识C语言
1.如何写C代码
①创建工程
②添加源文件:c文件:源文件,h文件:头文件
代码实例:
main函数是程序的入口,有且仅有一个
在C语言中,#include <stdio.h> 是一个预处理指令,用于将标准输入输出库(stdio.h)包含到程序中。这个库包含了一系列用于输入和输出的函数声明,例如printf和scanf等。通过包含这个头文件,你可以在程序中使用这些函数而不需要自己重新声明它们。
第一行代码
scanf函数是C语言提供的,VS编译器中相应的函数是scanf_s,所以在VS编译器中使用scanf会被警告(很多函数都会被警告)
#define _CRT_SECURE_NO_WARNINGS就是用来去除这个警告的
2.数据类型
类型 | 说明 |
---|---|
int | 整型,4字节 |
short | 短整型,2字节 |
long | 长整型,4字节 |
long long | 更长的整型,8字节 |
char | 字符类型,1字节 |
float | 单精度浮点数,4字节 |
double | 双精度浮点数,8字节 |
C语言没有String类型
3.变量和常量
变量的定义方式:类型 变量名;
其中:定义在main函数或者其他函数中的变量叫局部变量,定义在代码块({})之外的变量叫全局变量
局部变量和全局变量名字相同时,局部变量优先
输入函数:scanf()
注意:C语言规定,定义变量必须在代码块的最前面
(1)变量的作用域
变量只能在它所在的代码块中使用,能使用该变量的代码块就叫作用域
全局变量的作用域是整个工程
(2)变量的生命周期
局部变量的生命周期:进入作用域生命周期开始,出作用域生命周期结束
全局变量的生命周期:整个程序的生命周期
常量的分类:
- 字面常量:3,4等
- const修饰的常变量(const给变量赋予常属性,所以叫常变量)
- #define定义的标识符常量,如:#define MAX 10
- 枚举常量
枚举
枚举关键字:enum
定义枚举常量:
enum NUM
{
1,
2,
3
};
其中:123是枚举常量,他们的值是他们的索引
4.字符串、转义字符、注释
"hello \n"这种双引号引起来的一串字符叫字符串字面值
注:字符串的结束标志是一个\0,在计算字符串长度时\0,不算做字符串内容
字符串可以放到字符数组里:char arr[] = “abc”;,其中arr就等于{1, 2, 3, \0}(手动在数组里加’\0’也可以)
strlen()函数可以返回字符数组的长度,要使用strlen函数,需要先引入string库:#include <string.h>
注意:对于一个末尾没有\0的数组,strlen函数返回的是一个随机值
C语言中的转义字符:
还有\xdd,其中dd表示2个十六进制数字,如:\x30 0
其中:水平制表符是四个空格,也就是一个tab的空格
注释方法:
- C语言的注释风格:/**/(不能嵌套,不建议用)
- C++的注释风格://
5.原码、负码、补码
原码按二进制位取反得到反码
反码+1得到补码
只要是整数,在内存中存储的都是二进制的补码
6.关键字
常见的关键字:
auto:局部变量前面默认会有auto(被省略),在代码块结束后变量会被自动销毁
register:寄存器,可以加在经常要使用的变量前,将变量存储到寄存器中(注:作用是建议把变量存到寄存器中,计算机的寄存器有限,不可能把所有数据都放到寄存器中)
signed:有符号的,int定义的变量默认是有符号的,前面的signed默认被省略
unsigned:被修饰的int变量永远是正数
typedef:定义类型,如:typedef unsigned int p_int,将p_int定义为unsigned int的别名
void:与java类似
static:静态的,修饰在局部变量前时,局部变量在所有代码结束前不会被销毁
~~~~~~
修饰在全局变量前时,全局变量只能在当前文件中使用
~~~~~~
修饰在函数前时,函数的外部链接属性变成了内部链接属性,即无法被外部声明
extern:声明外部符号,如:extern int num;,可以引用外部文件的全局变量
7.#define定义常量和宏
定义表示符常量:#difine 常量名 数值
定义宏:#difine 函数名(参数,…) (代码块),作用:简化代码的编写
8.指针
有一种变量是用来存放地址的,这种变量叫指针变量
取一个变量的地址:int* p = &a;(int*是p的类型)
其中:&是取地址操作符
:解引用操作符,对于上面那句代码来说,*p == a
可以通过p操作a变量
指针大小在32位平台是4个字节,在64位平台是8个字节
9.结构体
结构体关键字:struct
结构体类似于java中的对象
结构体语法:
struct Book
{
char name[1];
short price;
}
创建结构体变量:struct Book b1 = {‘C语言’, 10};
使用结构体变量中的元素:b1.name
获取结构体的指针:struct Book* pb = &b1;
箭头操作符:->,作用是获得指针指向的对象的元素,如:pb->name,就可以获得name
注意:name是一个数组,不能直接用b1.name进行赋值,要用strcpy(b1,name, ‘C++’),其中strcpy是string库中的,意思为string copy
二.分支和循环
1.分支语句
语句:分号之间的代码就是一个语句,空语句也是语句
(1)if
语法:
if (表达式)
{
语句
}
else if (表达式)
{
语句
}
else
{
语句
}
注意:{}可写可不写
悬空else:C语言中else会与离他最近的if匹配
在C语言中,表达式中写赋值语句不会报错且默认为true,容易写错,所以我们一般将num == 5写成5 ==num
(2)switch
语法:
switch (整型表达式)
{
case 整数:
代码;
break;
case 整数:
代码;
break;
[default:
break;
]
}
default是可选语句,用于处理非法数据
如果不加break,代码会全部执行
2.循环语句
(1)while
语法:
while (表达式)
{
语句;
}
可以写continue和break
{}可以省略
getchar函数可以从键盘中获取一个字符的ASCII码(stdio库中的函数)
putchar函数可以输出ASCII码对应的字符(stdio库中的函数)
代码实例:
(ch = getchar()) != EOF中的ch=必写,不然输入EOF也不会结束
注意:在C中字符类型是以ASCII码存储的,所以ch可以直接跟字母比较
注意:用完scanf后我们的输入缓冲区会残留一个\n,所以如果后面还要输入的话,在这个scanf后写一个while((ch = getchar()) != ‘\n’)清空缓冲区
(2)for
语法:
for(表达式1; 表达式2; 表达式3)
{
代码
}
{}可写可不写
建议在区间中选择前闭后开的形式
可以在for中写两个变量来控制,代码实例:for(x = 0, y = 0; x < 2 && y < 5; x++, y++)
(3)do…while
语法:
do
{
代码
}
while (表达式)
{
代码
}
do后的{}如果省略,那么do后只能写一个语句
随机数生成
stdlib库的rand(void)函数可以生成一个0-32767的随机整数
srand是用于设置随机数发生器的种子。它的原型如下:
void srand(unsigned int seed);
其中 seed 是一个无符号整数,用于初始化随机数发生器的种子.通常情况下,可以使用当前时间作为种子,以确保每次运行程序时产生的随机数序列都是不同的
代码实例:srand((unsigned int)time(NULL));
其中:time函数是time库中的函数用于获取当前的时间戳
3.goto语句
C语言中提供了可以随意滥用的goto语句和标记跳转的标号
从理论上goto语句是没必要的,没有goto语句也完成程序
goto语句最常见的用法就是终止程序在某些深度嵌套的结构的处理过程,例如一次跳出多层循环
代码示例:
for()
for()
for()
if()
goto error;
error:
if()
代码
其中:error可以是任何字符串,只是作为一个标识,用于跳转
其它函数
system(‘命令’):stdlib库中的函数,作用是在cmd中执行括号中的命令
strcmp(‘string’, ‘string’):string compare,string库中的,作用是比较两个字符串,如果这两个字符串相同,则返回0
三.函数
1.函数的调用
函数的调用分为两种:传值调用和传址调用
当你将一个变量作为参数传递给一个函数时,函数会得到这个变量的拷贝而不是原始变量本身
传址调用时函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式
这种传参方式可以让函数和函数外边的变量建立真正的联系,也就是函数内部可以改变传入的参数的实际值
如果传过去的是一个数组,那么传过去的不是整个数组,而是第一个元素的地址,第一元素的地址+1就是第二元素的地址
printf函数返回的输出的数据的长度
可以用#include '文件名.h’来引入其它C语言文件
2.关于函数的引入
#include会把文件中的代码copy过来,但是为了避免重复copy浪费资源,可以写
#ifndef __文件名_H__
代码
#endif
3.C语言的内存
C语言内存分为三块:栈区、堆区和静态区
栈区用于存储局部变量和函数形参
堆区用于存储动态开辟的内存,如:malloc和calloc
静态区用于存储全局变量和static修饰的变量
递归写得不好就很可能出现栈溢出的问题
四.数组
1.一维数组的创建和初始化
创建数组的语法:类型 数组名[数组长度(常量)] = {数据1, 数据2,…}
char arr[5] = ‘ab’;这是不完全初始化,其中arr的后三位都是0(第三位其实是\0
访问数组的元素:数组名[索引]
sizeof函数可以计算数据所占空间的大小(单位是字节),sizeof(arr)/sizeof(arr[0])即为数组的长度
注意:strlen可以返回数组中\0之前的长度
2.一维数组的内存布局
数组在内存中是连续的:
相邻元素的地址之间的差值通常是一个定值,取决于类型和计算机架构
3.二维数组的创建
语法:类型 数组名[数组长度][数组长度] = {数据1, 数据2,…};
代码示例:int arr[2][2] = {1, 1, 1};把第一行和第二行的第一位定义为1
或者也可以这样写:int arr[2][2] = {{1, 1}, {1}};
二维数组的其中一个长度可以不写,默认为初始化的长度
注意:二维数组的元素的地址也是连续的
4.数组作为函数参数
如果对数组进行传参,实际上传递过去的是数组的首元素的地址&arr[0]
注意:实际上这并不影响使用,arr[1]依旧能获取第二位元素,只是sizeof函数会受到影响,所以计算数组长度要在函数外
实际上:数组名的值就是首元素地址(有两个例外),对数组名进行解引用也能得到首元素
例外:
- sizeof(数组名),此时数组名表示整个数组
- &数组名,此时数组名代表整个数组,取出的是整个数组的地址(实际上是首元素的地址,但是给它+1时它会变成第二位元素地址)
不用第三个变量交换两个变量
写法1:
a = a + b;
b = a - b;
a = a - b;
问题:整形溢出
写法2:
a = a^b;
b = a^b;
a = a^b;
问题:可读性差,执行效率低
五.操作符
1.运算操作符
运算操作符:±*/%
int a = 5 / 2;输出2
int a = 5 % 2;输出1.000000(默认六位小数)
注意:%操作符不能作用于浮点数
2.移位操作符
移位操作符:左移<< 右移>>
>>的作用:
- 算术右移:右边丢弃,左边补原符号位(多数编译器的右移)
- 逻辑右移:右边丢弃,左边补0
int a = -1;
a >> 1;
上述代码返回-1
原因:整数按补码在内存中存储,-1->111111111…(省略),所以右移之后还是-1
注意:不能移动负数位
3.位操作符
位操作符:&|^
注:操作数必须是整数
作用:按二进制位与、或、异或
4.赋值操作符
赋值操作符:=
复合赋值操作符:+= -= *= /= %= >>= <<= &= |= ^=
5.单目操作符
单目操作符: