C语言知识点
1. 基本入门
-
下载的编译器
devc++ , clion , visual studio
-
编译器的基本使用
-
注释的作用及使用
用于注解说明解释程序的文字就是注释,注释提高了代码的阅读性
C 语言提供了两种注释的形式行注释和块注释。
-
行注释
//这是行注释
// 注释内容
-
块注释
C 语言块注释也可以用来注释单行代码
/* … */
/*这是块注释*/
-
-
主函数的概念
任何一个c程序总是从main函数开始执行
Int main(){…}
被大括号{ }括起来的内容称为main函数的函数体,这部分内容就是计算机要执行的内容
-
Hello World
#include <stdio.h> int main() { printf("Hello, World!\n"); return 0; }
2. 基本数据类型
-
整数型
Char, short, int, long, long long
-
浮点型
Float, double, long double
-
字符型
char
-
类型有何不同
- 类型名称:int , long, double
- 输入输出时的格式化: %d , %ld , %lf
- 所表达数的范围:char<short<int<float<double
- 内存中所占的大小:1~16个字节
- 内存中的表现形式:二进制(补码),编码
3. 运算符
-
算术运算符
除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
% 操作符的两个操作数必须为整数。返回的是整除之后的余数。
-
逻辑运算符
&&
逻辑与 理解为: 即 怎么 又 怎么 一假全假,全真为真
||
逻辑或 理解为: 要么 怎么 要么 怎么 一真为真, 全假为假
!
逻辑非 真取假 假取真
截断特性:
逻辑与左边为假,右边不在读取计算
逻辑或左边为真,右边不在读取计算
-
位运算
&
//按位与 与0得0
|
//按位或 或1得1
^
//按位异或 相同为0,相异为1
-
三目运算符
exp1 ? exp2 : exp3
exp1若为真,则执行exp2,否则执行exp3
-
优先级:
括号 > 单目运算符 > 算术运算符> 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 三目运算符 > 赋值运算符 > 逗号运算符
4. 表达式与语句
-
while /
和do while 的区别: do while 先执行,因此do while 至少执行一次代码块中的内容
do-while 循环至少要执行一次“语句块”
- while(1)表示无限循环
-
if
if(…) {…} else if(…) {…} … else if(…) {…} else {…}
-
for
for(表达式1; 表达式2; 表达式3){ 语句块 }
-
switch
switch (表达式){ case 目标值1: 执行语句1 break; case 目标值2: 执行语句2 break; ...... case 目标值n: 执行语句n break; default: 执行语句n+1 break; }//加上default程序会更保险,避免情况遗漏导致系统崩溃
-
goto
for(...) for(...) { for(...) { if(disaster) goto error; } } … error: if(disaster) // 处理错误的情况
过多使用可能导致逻辑混乱,系统崩溃等故障
-
跳出循环:
break 关键字用于 while、for 循环时,会终止循环而执行整个循环语句后面的代码。
continue语句的作用是跳过循环体中剩余的语句而强制进入下一次循环。continue语句只用在 while、for 循环中,常与 if 条件语句一起使用,判断条件是否成立。
当continue在while和for中时,运行到continue不会直接跳出循环,而是跳到判断条件处进行判断,条件满足时则继续循环。
5. 数组
-
一维数组 /
int number[] = {0};
//集成初始化集成初始化时的定位:
int a[10] = {[0] = 2, [2] =3 ,6}
在初始化时给0位置赋2,在2位置赋3,在3位置(没有说明直接接下去)赋6,没有得到值的位置均为0.
下标/索引 从 0 开始计数
-
一维数组地址
- 数组变量的名称代表的是这个数组的地址值
- 一个数组的地址实际上是第一个元素的地址值,取第三个也一样
-
二维数组
int a [3][5];
//通常理解成是一个3行5列的矩阵
二维数组的遍历for(int i =0 ; i<3 ; i++){ for( j =0 ; j<5 ; j++){ a[i][j] = i*j; } }
a[i][j]
意思是第i行第j列上的内容
a[i,j] //意思是 a[j],逗号在这里是运算符 -
二维数组地址
-
二维数组的数组名代表的也是数组的地址值,也是首地址值
-
特别注意:
在一维数组中a代表的是数组a的地址值,也是a[0]的地址值,对其进行取值运算*a得到的是a[0]的值;但是!:对二维数组来说
*a
仍然是一个地址值!!!
-
-
数组之间赋值的内存变化
-
多维数组
6. 指针
-
基本数据类型和指针的关系
int i ; int* p = &i ;//表示对i进行取地址并且把地址传给指针变量*p, 此时说p指向了i ,p存储的是变量i的地址 int* p,q;//*可以远离p,表示指针变量p和普通int变量q,不是指针变量p和q int *p,q;//*可以靠近p,仍然表示指针变量p和普通int变量q,也不是指针变量p和q //*p是一个int int *p,*q;//表示指针变量p和q
-
一级指针
平时常见的普通指针
-
二级指针
指向一级指针的指针(一个指针指向另一个指针),也称指向指针的指针
int a = 12; int * b = &a; //一级指针 int **c = &b; //二级指针,指针c指向的是指针b,也就是说二级指针c存储的是一级指针b的地址。
三级指针就是指向二级指针的指针,也就是说n级指针就是指向n-1级指针的指针
int a = 12; int * b = &a; //一级指针 int **c = &b; //二级指针,指针c指向的是指针b,也就是说二级指针c存储的是一级指针b的地址。 int ***d = &c; //三级指针,指针d指向的是指针c的地址
-
在看数组指针和指针数组之前先明确优先级顺序:
()>[]>*
-
指针数组->是一个数组,存放指针的数组
int a = 1; int b = 2; int *p[2]; p[0] = &a; p[1] = &b;
-
数组指针->是一个指针,指向数组的指针
//一维数组 int a[5] = { 1, 2, 3, 4, 5 }; //步长为5的数组指针,即数组里有5个元素 int (*p)[5]; //把数组a的地址赋给p,则p为数组a的地址,则*p表示数组a本身 p = &a;
-
malloc函数
使用时引入头文件
#include"malloc.h"
如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL
malloc函数的返回的是无类型指针,在使用时一定要强制转换为所需要的类型,在使用malloc开辟空间时,使用完成一定要释放空间,如果不释放会造内存泄漏
在使用malloc函数开辟的空间中,不要进行指针的移动,因为一旦移动之后可能出现申请的空间和释放空间大小的不匹配举例: 开辟一个10个int大小的空间
int *p = (int *)malloc(sizeof(int) * 10);
在使用malloc函数之前我们一定要计算字节数,malloc开辟的是用户所需求的字节数大小的空间
-
free函数
释放malloc(或calloc、realloc)函数给指针变量分配的内存空间
使用后该指针变量一定要重新指向NULL,防止悬空指针(失效指针)出现
接着上面举例:
free (p); p = NULL;
-
其他函数(了解)
7. 函数
-
函数的基本概念及使用
-
函数的定义:
函数的基本组成部分
//每个c语言必备的函数,main函数举例: int main() { return 0; } /* int: 返回值类型 main:函数名-->标识符,就是一个名字,想起什么就起什么 ():描述参数 return:函数返回值 函数返回值类型。函数名(行参) { 函数体 return 返回值; } 注意点: 1. 函数返回值类型必须喝return后面的数据类型一致 2. 函数中没有,可以用void表示 3. return的作用是结束当前函数,遇到return函数就结束,return后面的不会执行 4. 函数传入参数是一种隐含的赋值操作 */ int main(void) { return 0; } void main() { //没有return 0,因为void表示没有,这个函数没有返回值 }
函数调用:
函数名(函数参数)
函数参数必须要和你定义的时候的参数类型一致
- 形参(形式参数):函数定义,声明时候用的参数
- 实参(实际参数):函数调用用的参数
#include "stdio.h" void print (int a) { printf("我是函数\n"); return; printf("abc");//这里abc不会被输出,因为函数在return被结束了 } int main(){ print(1); return 0; }
-
-
return机制
return 表示把程序流程从被调函数转向主调函数并把表达式的值带回主调函数,实现函数值的返回,返回时可附带一个返回值,由return后面的参数指定
return不是必须要返回一个值
void function() { ... return; /*此处的return表示中止当前函数的运行,并将操作权返回给调用者。 并返回一个状态码来表示函数执行的顺利与否。 */
-
函数地址
-
递归函数 ->自己调用自己的函数
每调用一次就进入新的一层,当最内层的函数执行完毕后,再一层一层地由里到外退出
举例:求阶乘:
long factorial(int n){ if(n == 0 || n == 1){ return 1; }else{ return factorial(int (n-1)) * n; } }
那么怎样进入递归:
比如求5的阶乘
-
求 5!,即调用 factorial(5)。当进入 factorial() 函数体后,由于形参 n 的值为 5,不等于 0 或 1,所以执行factorial(n-1) * n,也即执行factorial(4) * 5。为了求得这个表达式的结果,必须先调用 factorial(4),并暂停其他操作。换句话说,在得到 factorial(4) 的结果之前,不能进行其他操作。这就是第一次递归
-
调用 factorial(4) 时,实参为 4,形参 n 也为 4,不等于 0 或 1,会继续执行factorial(n-1) * n,也即执行factorial(3) * 4。为了求得这个表达式的结果,又必须先调用 factorial(3)。这就是第二次递归
-
以此类推,进行四次递归调用后,实参的值为 1,会调用 factorial(1)。此时能够直接得到常量 1 的值,并把结果 return,就不需要再次调用 factorial() 函数了,递归就结束了
退出递归:
- n 的值为 1 时达到最内层,此时 return 出去的结果为 1,也即 factorial(1) 的调用结果为 1
- 有了 factorial(1) 的结果,就可以返回上一层计算factorial(1) * 2的值了。此时得到的值为 2,return 出去的结果也为 2,也即 factorial(2) 的调用结果为 2
- 以此类推,当得到 factorial(4) 的调用结果后,就可以返回最顶层。经计算,factorial(4) 的结果为 24,那么表达式factorial(4) * 5的结果为 120,此时 return 得到的结果也为 120,也即 factorial(5) 的调用结果为 120,这样就得到了 5! 的值
-
8. 字符串
-
字符串的基本概念和使用
概念:以0(整数0)结尾的一串字符, 0或
\0
是一样的,但是和’0’不同- 0标志字符串的结束,但是它不是字符串的一部分
- 计算字符串长度的时候不包含这个0
- 字符串以数组形式存在,以数组指针的形式访问
- 更多的是指针的形式
- string.h里有很多处理字符串的函数
使用:
char a[6] = "hello";
->用数组char* a ="hello";
->用字符串指针,注意的是对s所指的字符串做写入会导致严重后果,用char s[] = "Hello, world!";
来做修改. - 0标志字符串的结束,但是它不是字符串的一部分
-
字符串相关属性的方法
-
字符串函数<string.h>
-
strlen
- size_t strlen(const char *s);
- 返回字符串s的长度(不包括结尾的0) ,但是sizeof包括结尾的0
-
strcmp
-
int strcmp(const char* s1,const char *s2);
-
比较两个字符串,返回:
- 0: a1 ==s2
- 1: a1>s2
- -1: s1<s2
-
注意:“==”比较的是内存里面的地址,不是字符串内容
-
strcpy
- char* strcpy(char* restrict dat,const char *restrict src);
- 把src的字符拷贝到dst
- restrict表明src和dst不重叠(C99)
- 返回dst
- 为了能链起代码来
-
strcat
- char* strcat(char* restrict s1,const char* restrict s2);
- 把s2拷贝到s1的后面,接成一个长长的字符串
- 返回s1
- s1必须有足够的空间
安全问题:
- strcpy和strcat都可能出现安全性问题
- 如果目的地没有足够的空间?
- 尽可能不要使用
安全的方法:(带n的版本)
- char* strncpy(char* restrict dat,const char *restrict src, size_t n);//n代表这个函数最多拷贝多少东西,多了掐掉
- char* strncat(char* restrict s1,const char* restrict s2,size_t n);//n代表这个函数最多拷贝多少东西,多了掐掉
- int strncmp(const char* s1,const char *s2,size_t n);//n是为了让它只判断前几个字符,如只比较前三个字符,n=3即可
-
字符串中找字符
-
char* strchr(const char *s,int c);
从左向右查找字符c第一次出现的位置,返回的是指针
-
char* strrchr(const char* s,int c);
从右向左查找字符c第一次出现的位置,返回的是指针
-
返回NULL、表示没找到
-
-
-
-
-
字符串copy的方法
使用for循环进行拷贝,把\0作为循环退出的条件
9. 内存(此处重要)
-
缓冲区
缓冲区是\内存空间的一部分。也就是说,在内存空间中预留了一定的存储空间,这些存储空间用来缓冲输入或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区
比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度
-
字符常量区
文字常量区相同字符串共用内存,可以有效防止重复内容占用内存
-
堆(heap):
由程序员手动分配和释放的存储区,如果不手动释放该空间会被一直占用,c语言中使用malloc,free申请和释放空间
-
栈(stack)
指那些由编译器在需要的时候分配,不需要时自动清除的变量所在的储存区,如函数执行时,函数的形参以及函数内的局部变量分配在栈区,函数运行结束后,形参和局部变量去栈(自动释放),速度快,内存有限.
-
变量的作用范围
10. 结构体
定义结构体:
struct book{
char title[100];
char author[100];
};
typedef struct book{
char title[100];
char auther[100];
}book //使用typedef给结构体进行重命名,调用时用book即可
链表(单链表):
由数据域和指针域组成,指针域放指向下一个节点的指针
typedef struct Node{
struct Book book;
struct Node* p_Next;//指向下一个节点的指针
}node;
11. 枚举(了解)
c: 可以理解成: 给整数取名字
#include "stdio.h"
int main(){
enum fruit{apple,orange,banana,purple}a,b,c,d;
//可以理解成apple代表1,orange代表2....
//是int的一个子集,sizeof(enum fruit) == 4;->tuue
a=apple;
b=orange;
c=banana;
d=purple;//把枚举值赋予变量a,b,c,d,但是a=1这样的写法是不对的
printf("%d,%d,%d,%d\n",apple,orange,banana,purple);
printf("%d,%d,%d,%d\n",a,b,c,d);
return 0;
}
//两句printf输出结果均为是1,2,3,4
//如何定义一个变量让其获得枚举里边的数?
enum fruit co = apple;//这种写法通用,效果是int co = 1;
int co = apple;//仅在c使用,c++不行
enum fruit co = 63;//这样写可以吗?,可以,因为enum是int的子集
Java:新建时选择enum
12. 类型转换
-
隐式转换
什么是隐式转换:
当一个值拷贝给另一个兼容类型的值时,隐式转换会自动进行。所谓隐式转换,是指不需要用户干预,编译器私下进行的类型转换行为。
标准转换:
short a =2000; int b; b = a;
这里a在没有任何显示操作符的情况下由short类型转换为int类型,就是标准转换,标准转换将影响基本数据类型,允许数字类型之间的转换(short到int, int到float, double到int…),指针到指针等.
对于非基本类型,数组和函数隐式地转换为指针
- NULL指针可以转换成任意类型的指针
- 任意类型的指针可以转换为void指针
- 指针向上提升一个派生类指针可以被转换为一个可访问的无歧义的基本类型指针不会改变它的const属性和volatile属性
-
显式转换(也叫做强制转换):
double类型转换成int类型,如int num1 = 3.14(3.14默认为double类型)int是整数型,是不带小数点的,然而在double类型中是带小数点之后两位的,如果要想让这行代码成立,则需要强制转换
num1 = (int)3.14
13. 关键字
-
extern
extern在中文里代表外部的、对外的。
该关键字常常被用在全局变量、函数或者模板声明中,表示该符号具有外部链接属性。
跨文件的时候,写头文件用extern声明减少寻不到的错误
-
static(重点)
static变量默认初始化为0,全局变量也是,因为这些变量被存储在静态存储区,在静态存储区的变量初始值均为0,内存中所有的字节默认值都是0x00
加static和不加的区别:
不加static的变量具有全局可见性
-
不加static
#include <stdio.h> void test() { int num = 0; num++; printf("%d ", num); } int main() { int i = 0; for (i = 0; i < 10; i++) { test(); } return 0; } //输出1 1 1 1 1 1 1 1 1 1
-
加static
#include <stdio.h> void test() { static int num = 0; num++; printf("%d ", num); } int main() { int i = 0; for (i = 0; i < 10; i++) { test(); } return 0; } //输出 1 2 3 4 5 6 7 8 9 10
-
-
const
-
关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变
-
用const修饰变量时,一定要给变脸初始化,否则之后就不能再进行赋值了
-
指针常量与常量指针:
常量指针是指针指向的内容是常量(所指是const)
const int * n; int const * n;
指针常量是指指针本身是个常量,不能在指向其他的地址(指针是const)
int * const n;
-
-
volatile(了解),在Java文件夹里面有单独说明
- volatlile保证了可见性,有序性
- volatile不支持原子性,适用于读多写少的场景
- volatile关键字修饰变量的三个特性:
- 可见性
- 有序性
- 受限原子性
-
restrict(了解):
只针对于c99使用,c++不支持,可以加入“-std=c99”从而实现对c99的支持
用于告知编译器所有修改指针所指向的内容必须是基于指针的,不存在其他的修改操作的途径
14. 文件操作
-
文件的打开和关闭:
//打开文件 FILE * pf = fopen ( const char * filename, const char * mode ); //关闭文件 fclose ( pf);
1.FILE为C语言提供的文件类型,它是一个结构体类型,用于存放文件的相关信息。FILE*即为文件指针类型,通过该指针,我们可以对其相关联的文件进行一系列操作。
2.为了打开文件,文件名是不可缺少的。如果要打开当前目录下的文件,可只用输入文件名,否则应该输入文件的绝对路径,如:c:\code\test.txt
3.mode为打开文件的几种方式,常见的有"r"(只读),“w”(只写),“a”(文件末尾追加),“rb”(二进制件打开,只读),“wb”(二进制件打开,只读),“ab”(二进制件打开,追加)等。
用"r"方式打开文件,若文件不存在,则返回一个空指针表示错误。若用"w"或"a"打开文件,则若文件不存在,都会创建一个新文件,即使文件存在,写入时也会把原有内容先进行覆盖
4.在对文件进行相关操作后应该及时使用fclose函数进行关闭,以便及时释放资源
-
字符输入函数fgetc():
fgetc函数返回文件指针指向的字符,并使指针向下一个字符偏移
-
字符输出函数fputc():
fputc('c',fp);//fp为文件指针
-
文本行输入函数fgets():
-
文本行输出函数fputs():