栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言学习

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言学习

记录一下学习C语言的过程,分享好的学习资料。

文章目录
  • 学习资料
  • 学习环境
  • 数据类型
    • 变量命名规范
    • 基本数据类型
    • 进制表示
    • 类型转换
    • 类型大小
    • 生存周期&作用域
    • 类型定义
  • 表达式
  • 控制语句
  • 数组
    • 一维数组
    • 二维数组
    • 变长数组 感觉是个假的:)
  • 字符串
    • 初始化
    • 常用函数
  • 函数
    • 函数声明 & 函数定义
    • 作用范围
    • 变长参数
  • 指针
    • 指针运算
    • 指针与数组
    • 指针与多维数组
      • 内存分布
      • int** 与 int (*)[3]的关系
      • 二维数组参数传递
  • 宏
    • 条件编译
  • 多文件
  • 结构、联合和枚举
    • 结构
    • 联合
    • 枚举
  • 内存管理
  • IO
    • 转译字符
    • 输出格式
    • 控制台IO
      • 格式化输入:scanf
      • 格式化输出:printf
      • 字符IO:getchar、putchar
    • 标准流和重定向
    • 文件IO

学习资料
名称类型描述链接
C Primer Plus书籍面面俱到,但内容较多,适合系统学习百度云,u46k
C 语言程序设计书籍简练一些链接

本次学习主要也是参考以上资源。

学习环境

学习阶段推荐使用:小熊猫devc

.c文件经过预处理、编译、汇编和链接后才能生成可执行程序,而集成环境(小熊猫devc)可以自动帮我们处理这些过程。

  • 预处理:指删除注释、替换宏和处理预处理指令(#include)
  • 编译:指将预处理后的c语言代码翻译成汇编码
  • 汇编:指将汇编码翻译成机器码(占位置,并不知道去哪里调用外部的函数和全局变量)
  • 链接:根据机器码链接,找到具体位置
数据类型 变量命名规范

可以使用大小写字母、下划线和数字,但第一个字符不能是数字,感觉是为了方便编译器设计:)
此外,不能使用关键字和保留字作为变量名。

  • 常量:全部大写,用下划线分隔
  • 变量名:全部小写,用下划线分隔
基本数据类型
  • 整型:short、int、long、long long
  • 浮点型:float、double、long double
  • 字符型:char
进制表示
进制数值十进制数IO符号
二进制0b113-
八进制0119%o
十六进制0x1117%x
类型转换
  • 隐式类型转换(向大范围转换不会有问题,向小范围转换会丢失)
    • 算术转换:提升到表达式中的最高级
    • 赋值转换、参数传递和返回值:浮点数到整数丢弃小数部分;大范围⇒小范围可能溢出;
  • 显示类型转换:用于防止溢出((long long)1000000*1000000)
类型大小
sizeof value;  // 作用在变量上
sizeof(char);  // 作用在类型上要加括号
生存周期&作用域
  • 局部变量:生存周期为代码块
  • 静态变量:始终存在,但对于代码块外不可见
  • 全局变量:始终存在,整个.c可见
类型定义
typedef int ID;  // 定义新的类型
表达式

运算符:+、 -、 *、 /、 %
比较算法:<、 >、<=、 >=、==、!=
逻辑算符:||、 &&
自增、自减:a++ ++a a-- --a
三元组运算符:表达式1? 表达式2: 表达式3
短路运算:对于逻辑运算符 && 和 ||有短路的效果
逗号表达式:常用于声明变量

控制语句

if体系、swtich、for、while和do-while

数组 一维数组
#define N 1000 // 配合数组使用

// 不初始化
int a[N];

// 初始化
int a[N] = {0}; // 不足的部分会用0填充
int a[] = {0, 1, 2, 3}; // 由{}中的值确定数组大小
二维数组
#define N 100

// 不初始化
int a[N][N];

// 初始化
int a[2][2] = {{0, 1}, {2, 3}};
int a[2][2] = {0, 1, 2, 3}; // 一维的形式初始化(不推荐)
int a[2][2] = {0} // 不足的补0(推荐这个清0)
变长数组 感觉是个假的:)
int n = 10;
int a[n]; // 可以用变量来指定长度
字符串 初始化
char s[] = "ni hao"; // 相当于 char s[] = {'n', 'i', ' ', 'h', 'a', 'o', ''},可以修改
char *s = "ni hai";  // s指向字符串常量,不能修改 
常用函数
int strlen(char *s);
int flag = strcmp(char *s0, char *s1);
strcpy(char *s0, char *s1);
strcat(char *s0, char *s1);
函数 函数声明 & 函数定义
// 函数声明
int add(int a, int b);

// 函数定义
int add(int a, int b)
{
	return a + b;
}
作用范围

当函数被static修饰时,说明该函数只能被本.c文件访问,可以减少名字污染。

变长参数

需要编译器配合,单参数列表为

#include 
#include 

void show(int num, ...){
	va_list ap;
	va_start(ap, num);
	
	for(int i=0; i
	show(4, 1, 2, 3, 4);
	
	return 0;
}
指针

保存变量的地址,可用于构建链表和树,此外还能在函数调用时减少参数传递开销。

int a = 1;

// 定义和赋值
int *p = &a;

// 可以通过指针赋值 ⇒ a = 2
*p = 2;

// 指针大小(64位--> 8)
printf("size: %dn", sizeof (int*));
指针运算

指针能进行加、减运算(包括自增、自减),编译器在编译时将改变运算的数值:对于char *,编译器判断出一个单位是一个字节,所以+1在编译后还是+1;而对于int *,编译器判断出一个单位是四个字节,所以+1在编译成汇编码后变成了+4。

char *p_c = 0;
int *p_i = 0;


printf("%pn", p_c); // 0
printf("%pn", p_c+1); // 1

printf("%pn", p_i); // 0
printf("%pn", p_i+1); // 4
指针与数组

数组相当于指向不可变的初始化了内存区域的指针。
a[1] 相当于 *(a+1),为了准确获得+1的便宜,必须要知道该指针的类型。

int a[10];
int *p = a;

// 指针可以按数组的格式使用
a[0] = 1;
printf("%dn", *p); 
printf("%dn", p[0]); // p[0] == *(p+0) == *(a+0) == a[0]

// 不可以改变数组的指向
int b[10];
a = b; // wrong, 所以a就相当于 int * const q = (int*)malloc(sizeof(int) * 10);
指针与多维数组

想要准确理解多维数组和指针的关系,就要理清楚数组在内存中的分布以及int** 与 int(*)[3]的关系。

内存分布
  • python的list是多级引用的结构,顶层的list指向子层的list;
  • C中的数组在内存中则是线性的。

int** 与 int (*)[3]的关系

如上图,虽然a、a[0]指向相同的地方,但两种的数据类型不同。a是int(*)[3],a[0]是int *,在提取数值时:**a == *a[0],从这个角度看 int** 好像就等于 int(*)[3]。

int a[2][3];
int (*p)[3] = a;  // a的数据类型是int (*)[3],所以接收时也要是该类型
int *q = a[0]; // 而a[0]则是int *的类型

int b = **p; // int (*)[3]是二级指针的一种特例,所以取值时要**
int c = *q; // q就只需要一个*

然而,两者还是有质的不同的,int**是真正的二级指针,指向int的指针;而int (*)[3]多少就有点冒牌的意味了。

int a[2][3] = {0, 1, 2, 3, 4, 5};

int (*p)[3] = a;
printf("%pn", p);   // 000000000066FE00
printf("%pn", *p);  // 000000000066FE00,与上面的一致,即*p并没有去访问p指向的内存
printf("%pn", **p); // 0000000000000000,这里才真正去做了访问

观察汇编码可以发现,在数值方面,*p和p的结果是完全一致的,可以说*p只是做了数据类型的改变,没有按照一般指针的形式访问数据。这点与前面提到的a与a[0]地址值相同呼应(a[0] == *(a+0)==*a)。在面对指向数组的指针时,*的作用是改变类型,而随着类型的改变,地址+1所增加的偏移也随之改变。具体来说就是a[1][1]中第一个1和第二个1带来的偏移量不同。而对于int**来说,则是真正通过两次寻址。
由于上述不同,当二维数组作为参数时,不能简单的形参定义为int **,而实际传递的又是int(*)[3]之类的二维数组。

二维数组参数传递
#include 


 
void show(int **a, int n, int m)
{
	int (*p)[m] = (int (*)[m]) a;
	
	for(int i = 0; i < n; i++){
		for(int j = 0; j < m; j++){
			printf("%d ", p[i][j]);
		}
		putchar('n');
	}
}

int main()
{
	int a[2][3] = {0, 1, 2, 3, 4, 5};
	show((int **)a, 2, 3);
	
	int b[3][4] = {{0, 1, 2, 3},{4, 5, 6, 7}, {9, 8, 7, 6}};
	show((int**)b, 3, 4);
	
	return 0;
}
宏
#define PI 3.14 // 定义常数
#define MAX(x, y) ((x)>(y)?x:y) // 宏函数: 参数列表必须紧紧贴合宏名


条件编译
// 适合找debug
#define DEBUG 1
#if DEBUG
	// do
#endif

// 适合debug
#define DEBUG
#if defined DEBUG
 	// do
#endif

// 简写
#ifdef DEBUG
 	// do
#endif

// 取反(适合排除互相包含)
#ifndef __MATH_H__
#define __MATH_H__
暴露给外界的全局变量和函数声明
extern float PI;
int add(int a, int b);
#endif
多文件

掌握共享全局变量和函数的原理和实际操作。

// people.c:有一个全局变量 num_people和函数greet()
int num_people = 0;

void greet()
{
	printf("Hello, boy!n");
}

// main.c:需要访问num_people并且使用greet
extern int num_people; // 声明变量类型,即可使用该变量
void greet(); // 声明函数原型,即可调用该函数



结构、联合和枚举 结构
// 定义了struct student的类型
struct student {
	int id;
	char name[32];
};
struct student stu1; // 定义变量:不能省略struct

// 用typedf化简
typedef struct student {
	int id;
	char name[32];
} Student;

Student stu1 = {12, "fyy"}; // 定义变量
printf("%dn", stu1.id); // 通过.访问

Values * p = &stu1; 
printf("%dn", p->id); 对于指针,通过->访问, 就相当于: (*p).id
联合
typedef union values{
	int int_v;
	double double_v;
}Values;

Values v; // 注意初始化方式
v.double_v = 1.234; // 通过.访问
枚举
typdef enum size {
	BIG, MIDDLE, SMALL
} Size;

Size = BIG; // 直接用
内存管理
// 不会堆申请区域初始化
int *arr = (int *) malloc(sizeof(int) * 100); // 申请
free(arr); // 释放

// 对申请区域初始化
int *arr = (int *) calloc(100, sizeof(int)); // 初始化为0

// 调整大小
arr = (int*) realloc(arr, sizeof(int) * 200); // 调整大小
IO 转译字符

常用转译字符: t制表、n换行
空白符:t、 n、 空格

输出格式

输出%:需要连续的%%
输出格式:根据%[标志][最小字段宽度][.精度][转换说明符]控制

  • 常见标志:-表示左对齐
  • 最小字段宽度:表示输出至少占多少宽度(不足补空格)
  • 精度:表示至少有多少位(不足补0),整数考虑所有位数;浮点数只考虑小数位数
  • 转换说明符
类型scanfprintf
int%d%d
long%ld%ld
long long%lld%lld
float%f%f
double%lf%f or %lf
char%c%c
控制台IO 格式化输入:scanf

输入匹配:将控制台的输入理解成流(拼接成串),根据输入格式串去匹配。

  • 匹配转换说明符时:刚开始遇到空白符时,跳过直到遇上非空白符,当遇到匹配不上的符号时,将该符号留在流中,结束匹配。
  • 匹配空白符:可以匹配输入中任意数量的空白符(可以是0个)。
  • 匹配非空白符:直接匹配,如果匹配不上直接退出,并将其留给下一次匹配。
scanf("%d%d", &a, &b); 
// 输入: 10nn20n ⇒ a = 10, b = 20,剩余输入串: n
// 输入:10-20n ⇒  a = 10, b = -20,剩余输入串:n
// 输入:10a20n ⇒ a = 10, b 原始值,剩余输入串:a20n

scanf("%d   %d", &a, &b); 
// 输入: 10nn20n ⇒ a = 10, b = 20,剩余输入串: n
// 输入:10-20n ⇒  a = 10, b = -20,剩余输入串:n
// 输入:10a20n ⇒ a = 10, b = 原始值,剩余输入串:a20n

scanf("%da%d", &a, &b); 
// 输入: 10n20n ⇒ a = 10, b = 原始值,剩余输入串: n20n
// 输入:10a-20n ⇒  a = 10, b = -20,剩余输入串:n
// 输入:10b20n ⇒ a = 10, b = 原始值,剩余输入串:b20n

scanf("/%d", &a);
// 输入:/1234n ⇒ a = 1234, 剩余输入串:n
// 输入:nn/1324n ⇒ a = 原始值, 剩余输入串:nn/1324n

scanf(" /%d", &a);
// 输入:/1234n ⇒ a = 1234, 剩余输入串:n
// 输入:nn/1324n ⇒ a = 原始值, 剩余输入串:n
格式化输出:printf
printf("%dn", 520); // 根据输出格式,直接输出即可。
字符IO:getchar、putchar
char c = getchar(); // 输入一个字符
putchar(c); // 输出一个字符
标准流和重定向

C有三个标准流,不需要对其打开关闭即可使用。

名称含义设备
stdin标准输入键盘
stdout标准输出屏幕
stderr标准错误屏幕

当用cmd运行exe文件时,可以将文件重定向为上述的三个标准流。在cmd中输入./run.exe out.txt,则在运行时将in.txt中的数据当作键盘输入,out.txt当作屏幕输出。

文件IO
  • 打开文件:FILE * fp = fopen(path, mode);
  • 关闭文件:fclose(fp);
  • 向文件输出:fprintf(fp, 格式化串, ...);
  • 由文件输入:fscanf(fp, 格式化串, ...);
  • 到结尾:feof(fp);
#include 
#include 

int main()
{
	// 写文件
	FILE *fp;
	if((fp = fopen("test.txt", "w")) == NULL){
		printf("fail to open!n");
		exit(1);
	}
	
	for(int i=0; i < 10; i++)
		fprintf(fp, "%dn", i);
	fclose(fp);
	
	// 读取文件
	if((fp = fopen("test.txt", "r")) == NULL){
		printf("fail to open!n");
		exit(1);
	}
	
	int num;
	while(fscanf(fp, "%d", &num) != EOF){
		printf("%dn", num);
	}
	fclose(fp);
	
	return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1019394.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号