介绍C
C是高级语言中性能最高的开发语言,使用也比较广泛,偏硬件开发。
用于开发操作系统、或者其他语言等。具有如下特性:
- 高效性;
- 灵活性;
- 功能丰富;
- 表达能力强;
- 移植性好;
开发环境
- 文本编辑器: 编辑source.c源文件(文本);如vim / vs code编辑器
- 编译源代码,使用gcc编译器
window: 需要安装minGW软件
linux: 安装sudo apt-get install gcc
# 命令行下直接编译
gcc source.c -o xxx.exe # win
gcc source.c -o xxx.out # linux
在window下默认编译为.exe
可执行程序;
在linux下默认编译为.out
可执行文件
有多个源文件时,要编译所有的源文件:
# 编译所有的源文件
gcc a.c b.c ...
也可以通过-o指定编译输出的文件名:
# -o 指定输出文件名
gcc source.c -fPIC -o source.dll
C开发中使用集成开发环境Visual Studio下载地址,vs是一个编译器,而vs code是一个编辑器,仅可以编辑代码,并不能编译。
vs快捷键:
ctrl + k, c 注释一行;
ctrl + k, u取消注释;
选中多行,则注释多行
F5调试 -> F11 逐行调试 ->shift +F11 跳出逐行调试 -> shift + F5 结束调试;
ctrl + F5 直接执行;
第一个C程序
一般都是在Visual Studio创建一个工程。
- 创建项目
- 配置项目位置
- 添加源文件(右键-添加-新建项),并命名为xxx.c
// helloWorld.c
#include <stdio.h> //标准输入输出头文件
// 定义一个返回int的主函数,程序的入口,只能一个
int main(){ // void 写法过时
printf("hello world\n");
// 函数体结束,return int
return 0;
}
调试时,若窗口一闪而过,则工程项目右键 > 属性 >:
结构说明:
- 面向过程,从main函数开始执行,依次执行所有的其他功能函数;基本单元是函数;
- 语句以
;
结束 - 大写字母表示常量,m_xxx表示成员(小驼峰),大驼峰表示函数;
- 空格、空行表示规范;
- 程序:算法+数据结构
-
伪代码 描述算法,
-
流程图 描述算法,椭圆表示起始、结束,矩形表操作,菱形表判断,平行自边形表输入、输出,
-
N-S流程图 描述算法,顺序结构,选择结构,循环结构
-
基本数据类型
-
整型;
- short,2bytes;
- int ,
声明变量则分配内存
,4bytes(32bit 操作系统); - long,4bytes
- long long 8bytes;
-
浮点型;
- float,4bytes;
- double,8bytes;
-
字符char, 必须单引号 ‘a’,1bytes;
-
枚举类型
- enum colors{ red, blue, yellow}
- enum colors{ red, blue, yellow}
-
其他扩展类型
- bool, true/false 必须包含头文件<stdbool.h>
- 字符串,strlen(char* p) 计算字符串长度 必须包含<string.h>
sizeof 计算变量的字节数,只能在函数外、main函数中使用;
-
其他类型
- 数组、指针(地址)、结构体、共用体、NULL
在计算机中,数值以补码形式存储,正数的补码为原码,负数的补码是其绝对值的原码按位取反 + 1;
char sex = 'M'; // 对应ASCII码整数值
printf("%c\n", sex);
int age = 23;
printf("%d\n", age);
// c中 没有 字符串 类型,只有字符数组
char a[] = {'a', 'b'};
printf("%p\n", a); // 输出数组的地址
printf("%s\n", a);// 从首地址开始,输出字符串
float score = 89.5f; // 创建变量,即分配内存 默认的小数是双精度
printf("%f\n", score); // %lf 双精度
常量
- const 定义常量 值不发生变化;
const int AGE = 20;
- #define 可以定义宏常量;
// 无参宏,编译时进行文本替换
#define YEAR 2013
- 字符常量
\\ \ 字符
\’ ’ 字符
\" " 字符
\? ? 字符
\a 警报铃声
\b 退格键
\f 换页符
\n 换行符
\r 回车
\t 水平制表符
\v 垂直制表符
\o 八进制数
\x 十六进制数
#define与const的区别:
替换机制:#define 是进行简单的文本替换,而 const 是声明一个具体类型的常量,运行时分配内存;
类型检查:#define 不进行类型检查,而 const 定义的常量具有类型信息,编译器可以对其进行类型检查;
作用域:#define 定义的常量没有作用域限制,整个代码中都有效,而 const 定义的常量具有块级作用域,作用域内有效。
运算符
- 输入、输出
//输入
int a, b; // --<
scanf("%d%d", &a, &b); // &获取变量的地址
int c; //
c = a + b;
printf("c = %d\n", c);
// 向控制台输出字符
putchar('a')
// 输入一个字符
getchar() // 输入字符后,回车是获取的下一个字符
//输入字符串
gets(char *p) // 存入地址
// 输出字符串
puts(char* p)
// 获取10个字符,存入ptr地址;
scanf("%10s", ptr) // ptr可以是一个数组首地址或者初始化的指针
// 打印
printf("%s\n", ptr)
- 作用域
局部变量作用域是局部范围代码块;
全局变量作用域是整个工程;
跨源文件访问全局变量使用extern声明;
//声明是在外部定义的变量
extern int a;
- 位操作符
& 按位与,同1才为1;
| 按位或, 有1则为1;
^ 异或, 相同则为0,不同则为1;
~ 按位取反, 0->1 , 1-> 0
>> 按位右移, 正数左边补0,负数左边补1;
<< 按位左移,右边补0
以上均可以与=赋值运算符结合使用,如 |=
#include <stdio.h>
int main()
{
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c );
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c );
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c );
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c );
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c );
}
- 各个运算符的优先级
int ageA = 100;
int a = 10;
int bb = a * ++ageA; // ++ -- 优先于 * /
printf("bb: %d\n", bb); // 结果:1010
int cc = a * ageA++; //同样是ageA++ 优先计算,只不过这里是先取ageA的值*a, 完成赋值后ageA 再++
printf("cc: %d\n", cc); // 结果:1000
数组
- 相同类型 数据的集合;
- 通过索引访问元素;
- C声明数组,即分配内存;
- 数组的名称,即首地址(首个元素的地址);
- { } 初始化与声明同时使用;
int arr[10]; // 分配40bytes
double arr1[10] = {2.0, 4.5}; // 声明并一行内初始化,最多存储10个数
// 声明后,后续再初始化只能使用索引方式。
float arr2[] = {1.0, 2.5}; // 只能存储两个数
//输出数组元素
for(int i=0; i < sizeof(arr2) / sizeof(float); i++){ // 静态数组
printf("%d\n", arr2[i]);
}
宏定义,计算数组的长度
#include <stdio.h>
#define LENGTH(array) (sizeof(array) / sizeof(array[0]))
int main() {
int array[] = {1, 2, 3, 4, 5};
int length = LENGTH(array); // 无法计算函数形参数组的长度
printf("数组长度为: %d\n", length);
return 0;
}
多维数组,
// 二维数组
int arr[3][4];
//声明,并初始化
int arr2[3][2] = {
{1, 3}, // 内层{}可以省略
{3, 7},
{1, 10}
}
// 访问、赋值
printf("访问:%d\n", arr2[0][1]);
// 三维数组
int arr3[4][5][3]
枚举类型
- C基本类型,使用enum定义;
- 成员变量的值默认从0开始,逐一+1;也可指定某个变量的值;
- 值连续的枚举可以遍历,否则不可遍历;
- 定义方式
enum Color{ // Color枚举名称,可省略
RED, // 默认从0开始,逐个+1; 可以赋值从1开始
YELLOW,
GREEN
}color; // 同时声明变量,[可省略]
int main(){
color = RED;
enum Color color2;
color2 = YELLOW;
printf("color: %d %d\n", color, color2); // 0 1
}
连续枚举的遍历:
#include <stdio.h>
enum Day
{
MON=1,
TUE,
WED,
THU,
FRI,
SAT,
SUN
} day;
int main()
{
// 遍历枚举元素
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
}
强制转为枚举 :
int num = 1;
//转为枚举类型
enum Day d;
d = (enum Day) num;
程序结构
- 主函数,在C项目中只能有一个源文件中有且仅有一个main函数,它是项目的入口函数。
- 顺序,C是面向过程,按照主函数顺序依次执行;未定义的函数必须先声明,才可以使用。
- 循环
- for循环
- while 循环
- do while 循环
for(int i = 0; i < 10; i++){
printf("i value:%d\n", i);
}
int a = 0;
while(a < 10){
a++;
printf("a value: %d\n", a);
}
int a = 0;
do {
a++;
printf("a value: %d\n", a);
}while(a < 10);
- 分支
- if分支
- switch分支
- 三目运算符 a ? a :b
// if 分支
double score = 87.5;
if(score >= 90){
printf("优秀");
}else if(score >= 80){
printf("良好");
}else if(score >= 70){
printf("中等");
}else if(score >= 60){
printf("及格");
}else{
printf("不及格");
}
//switch分支
int a = 3;
switch(a){
case 1: xxx; break;
case 2: xxx; break;
default: xxx;
}
存储类
存储类,用于表示变量存储类别。
- auto, 只修饰函数的局部变量,默认即为auto;
- register,将变量定义在寄存器内,访问更快;
- extern,外部变量,使用时向外部其他源文件找(不分配内存)。
- static,修饰全局变量(或者函数),则该 变量/函数 只能在
该源文件内
使用;
修饰局部变量,每次函数调用时,其值不会重置(仅初始化一次);
// 例子
int gg; // 定义一个全局变量(所有函数外面),分配4bytes内存
static int age; // 只能在本源文件中使用,其他源文件无法使用(extern声明也不行)
void func(int a){
auto int b; // 函数内部 默认定义局部变量
//声明外部变量
extern int gg; // 函数内部找全局 或 本模块找其他模块
// 寄存器变量
register char c;
}
预处理器
- 编译之前的文本替换操作;
- 预处理器
命令
以#开头;
#define, 定义宏常量
#include,包含头文件
#undef,取消已定义的宏
#ifdef,如果宏已经定义,则返回真
#ifndef,如果宏没有定义,则返回真
#if xxx 如果给定条件为真,则编译下面代码
#else,#if 的替代方案
#elif,如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码
#endif,结束一个 #if……#else 条件编译块
#error 当遇到标准错误时,输出错误消息
#pragma 使用标准化方法,向编译器发布特殊的命令到编译器中
#ifdef DEBUG
/* Your debugging statements here */
#endif
#ifndef YEAR
#define YEAR 5
#endif
// 两者属于互斥的分支
在编译时,可向 gcc 编译器传递 -D DEBUG 开关宏常量(#ifdef DEBUG),它定义了 DEBUG,可以在编译期间随时开启或关闭调试。
- 预定义宏常量
_DATE_ 当前日期,一个以 “MM DD YYYY” 格式表示的字符常量。
_TIME_ 当前时间,一个以 “HH:MM:SS” 格式表示的字符常量。
_FILE_ 当前文件名,字符串常量。
_LINE_ 当前行号,十进制常量。
_STDC_ 当编译器以 ANSI 标准编译时,则定义为 1。
#include <stdio.h>
main()
{
printf("File :%s\n", __FILE__ ); // test.c
printf("Date :%s\n", __DATE__ ); // Jun 5 2023
printf("Time :%s\n", __TIME__ );
printf("Line :%d\n", __LINE__ );
printf("ANSI :%d\n", __STDC__ );
}
- 宏延续运算符 \
宏定义换行; python中代码延续;
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
// 参数化的宏,类似函数 去除类型 和 {}
-
宏字符串常量化 #,在上个例子中 #a可以将传入的任何参数,转为字符串常量;
如message_for(HELLO, TOM) --> “HELLO”、“TOM” -
宏粘贴 ##
name##n,将n值粘贴与name一起形成一个变量名; -
宏defined运算符,判断一个宏是否定义;
#if defined(HELLO)
#undef HELLO
#else
#define LAUF(arr) (sizeof(arr) / sizeof(arr[0]))
#endif
- 参数化的宏,类似函数
#include <stdio.h>
// 参数化的宏, 类似函数去除类型 {} return等
#define MAX(x,y) x > y ? x : y
int main(void)
{
printf("Max between 200 and 100 is %d\n", MAX(200, 100));
return 0;
}
输入与输出
scanf,输入
printf,输出
… 接口在<stdio.h>头文件中。
#include <stdio.h>
//
char username[100]; // C中声明变量、数组,即分配内存
char password[100];
// 若声明指针,未分配内存,无法直接使用未初始化的变量;
char* username;
char* password;
//输入用户名,存入username空间;
printf("输入用户名:\n")
scanf("%s", username); // scanf在标准头文件中,遇空格则停止读取;第一个参数为输入格式;第二个参数为地址;
printf("输入密码:\n")
scanf("%s", password); // 如果是变量,则需要&引用其地址
//输入整数
int a;
printf("输入整数:\n")
scanf("%d", &a); // %d等符号表示期望用户的输入类型
// 输入字符串 整型
char str[100];
int i;
scanf("%s %d", str, &i);
...
printf("用户%s登录成功\n", username); //数组名为首地址
//一次读入一个字符
char cc;
cc = getchar()
//一次输出一个字符
putchar(cc)
// 一次读入一个字符串
char str[100];
gets(str); //读入一个字符串,存入字符数组
puts(str); //将字符数组内容,一次性输出
使用scanf报错问题:
解决,在源文件的开头定义宏常量
#define _CRT_SECURE_NO_WARNINGS 1
使用未初始化的变量
问题
声明字符指针,不会分配内存,即未初始化。需要自己手动分配内存。
#include <malloc.h>
// 声明指针,并分配内存(初始化)
char* username = (char*)malloc(sizeof(char) * 100);
char* password = (char*)malloc(sizeof(char) * 100);
printf("输入用户名:\n");
scanf("%s", username); // 指针即地址
printf("输入密码:\n");
scanf("%s", password);
printf("用户%s登录成功;\n", username);
文件操作
文本文件、二进制文件;
操作方式:
写入:
#include <stdio.h>
// 初始化文件指针
FILE *fp = NULL; //或者手动分配内存 NULL必须大写
// 打开文件,相对目录
fp = fopen("config.txt", "w+"); // r w a r+ w+ a+ rb wb ab
// 打印一行到文件
fprintf(fp, "%s", "c is a char\n");
fputs("name is jack\n", fp);
// 输出一个(整数值)对应的字符
fputc(65, fp);
// 关闭文件
fclose(fp);
读取:
// 读取文件
FILE* fp = NULL; // 初始化文件指针
// 打开文件,返回文件指针
fp = fopen("config.txt", "r");
// 读取一个字符
char oneChar;
oneChar = fgetc(fp);
printf("读取一个字符:%c\n", oneChar);
// fgets 读取指定长度的字符串
char buff[100];
fgets(buff, 50, fp); // 从fp文件指针中读取49个字符,存入字符数组; 最多读取一行
printf("fgets读取的文件内容: %s\n", buff);
// fscanf 读取字符串
char buff2[100];
fscanf(fp, "%s", buff2); // 存入指定地址
printf("fscanf读取字符串: %s", buff2);
fclose(fp);
二进制IO,使用fread / fwrite
判断空文件
- 方法1,读取首字母,判断是否为-1,-1则为空;
- 方法2,fseek(fp, 0, SEEK_END); ftell(fp); 关闭文件后查看ftell的int返回值; 0 表示为空;
案例:
- 程序执行,让用户输入用户名、密码进行登录;
- 提示是否记住密码,若是,则下次执行免密登录;
- 用户信息记录到config.txt文件中;
- 登录成功,提示xxx登录成功;
知识点:
输入、输出、文件读写。
#include <stdio.h> // 输入输出、文件读写
// C中没有布尔类型,需要使用头文件
#include <stdbool.h>
// get length of char array
int lengthOfArr(char arr[]) {
return sizeof(arr) / sizeof(char);
}
// check char array equal or not
bool strIsEqual(char str1[], char str2[]) {
if (lengthOfArr(str1) == lengthOfArr(str2)) {
for (int i = 0; i < lengthOfArr(str1); i++) {
if (str1[i] != str2[i]) {
return false;
}
}
return true;
}
return false; //true is 1; false is 0; in C 0 is false, other is true;
}
// 定义用户类型--结构体
typedef struct {
char username[100];
char password[100];
}User;
User checkSavePassword(void) {
FILE* fp = fopen("config.txt", "r+");
User user;
//long pos;
移动文件指针
//fseek(fp, 0, SEEK_END);
//pos = ftell(fp);
//fclose(fp);
// 查看pos大小,判断是否为空文件
//printf("文件指针位置:%d", pos);
char ch = fgetc(fp); // 通过首字母是否为-1 判断文件是否为空;
if (ch == -1) { // 文件为空
user.username[0] = 0;
fclose(fp); // 关闭空文件
return user;
}
// 防止读取空文件
fseek(fp, 0, 0);
fgets(user.username, 1000, fp); // read oneline
fgets(user.password, 1000, fp);
fclose(fp); // 关闭文件
return user;
}
void savePassword(User user) {
FILE* fp = fopen("config.txt", "w");
fputs(user.username, fp); // 写入一行
fputs("\n", fp);
fputs(user.password, fp);
fclose(fp);
}
// 定义一个返回int的主函数
int main() {
// welcome info
printf("欢迎使用用户管理系统\n");
// check if save password
User user = checkSavePassword();
printf("username %s\n", user.username);
printf("password %s\n", user.password);
if (user.username[0] != 0) {
printf("用户%s登录成功", user.username);
return 0;
}
printf("输入用户名\n");
scanf("%s", user.username);
printf("输入密码\n");
scanf("%s", user.password);
printf("否记住我\n 1-是 2-否\n");
int label;
scanf("%d", &label);
if (label == 1) {
savePassword(user);
}
printf("用户%s登录成功\n", user.username);
return 0;
}
[下一篇]: C语言编程—函数与头文件