C语言编程---基础学习

介绍C

C是高级语言中性能最高的开发语言,使用也比较广泛,偏硬件开发。
用于开发操作系统、或者其他语言等。具有如下特性:

  • 高效性;
  • 灵活性;
  • 功能丰富;
  • 表达能力强;
  • 移植性好;
     

开发环境

  1. 文本编辑器: 编辑source.c源文件(文本);如vim / vs code编辑器
  2. 编译源代码,使用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创建一个工程。

  1. 创建项目在这里插入图片描述
  2. 配置项目位置
    在这里插入图片描述
  3. 添加源文件(右键-添加-新建项),并命名为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}
       
  • 其他扩展类型

    • 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 定义的常量具有块级作用域,作用域内有效。

运算符

  1. 输入、输出
//输入
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)
  1. 作用域
    局部变量作用域是局部范围代码块;
    全局变量作用域是整个工程;
    跨源文件访问全局变量使用extern声明;
//声明是在外部定义的变量
extern int a;
  1. 位操作符
    & 按位与,同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 );
}
  1. 各个运算符的优先级
    在这里插入图片描述
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 表示为空;

案例:

  1. 程序执行,让用户输入用户名、密码进行登录;
  2. 提示是否记住密码,若是,则下次执行免密登录;
  3. 用户信息记录到config.txt文件中;
  4. 登录成功,提示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语言编程—函数与头文件

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

laufing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值