📋 前言
🌈个人主页:Sarapines Programmer
🔥 系列专栏:本期文章收录在《C语言闯关笔记》,大家有兴趣可以浏览和关注,后面将会有更多精彩内容!
⏰翰墨致赠:墨激雷霆势,心随碧波飘。山河承豪情滔天,梦御风云志浩荡。
🎉欢迎大家关注🔍点赞👍收藏⭐️留言📝
🔔作者留言:欢迎来到我的【C语言】魔法学堂!这里是探索编程世界的秘境,我的学习笔记博客为你打开C语言的魔法之门。在这里,我不仅分享C语言的基础知识和高级技巧,还有着涉猎实用技术和项目经验的魔法药水。无论你是新手还是编程巫师,这个魔法堂会为你施展出奇幻的学习魔法,帮助你在C语言的魔法森林中踏上一场奇妙之旅。准备好了吗?跟着我,让我们一起编织属于自己的魔法代码!
目录
⛳️1. 第一章 程序设计和C语言
1.1 基本知识
程序:一组计算机能识别和执行的指令。
计算机语言:计算机和人都能识别的语言。
C语言是在B语言的基础上发展而来。
计算机语言发展阶段:
- 机器语言:计算机能直接识别和接受的二进制代码的集合(即机器指令的集合)。
- 符号语言(或汇编语言):使用英语字母和数字表示指令,再通过汇编语言将其转换为机器语言。如ADD表示“+”...
- 高级语言:使用英文单词表示语句和指令,需要编译程序将源程序(高级程序)转换为目标程序(机器指令程序)。
C语言特点:
- 语言简洁,使用方便灵活
- 运算符丰富
- 数据类型丰富
- 程序可移植性好
- 允许直接访问物理地址,进行位操作
- 生成的目标代码质量高,程序执行效率高
函数包括:函数首部(第一行)+函数体(‘{’+内容+‘}‘),函数体=函数声明(即函数原型)+执行部分。
注意易错:
- 每个C程序有且仅有一个main函数,程序从main函数开始执行且结束
- C语言注释方式://单行注释和/* 多行注释 */
- 一个程序由一个或多个源程序文件组成
- C程序编译运行步骤:源程序.c—编译—>目标程序.obj —连接—>可执行程序.exe
全局变量与局部变量同名:在局部变量作用域内,全局变量被“屏蔽”。
变量的静态存储方式和动态存储方式:
静态存储方式:变量的内存空间在程序运行之前就已经分配好,并且在整个程序的执行过程中都存在。这意味着变量的生命周期贯穿整个程序的运行时间。
静态存储方式主要包括:全局变量+静态局部变量
动态存储方式:涉及到在程序运行时动态分配和释放内存,通过使用特定的函数来完成,如
malloc
、free
等。动态存储的变量在程序运行时根据需要分配内存,因此其生命周期和作用域可以在运行时动态地确定。动态存储方式主要包括:自动变量+形参等等
程序的内存区域:
- 代码区:函数块
- 全局数据区:全局变量+静态局部变量【初始值默认为0】+常量
- 堆区:动态数据,如malloc、free【C语言】或new、delete【C++】
- 栈区:局部变量【无初始值】+形参+函数返回值+保护现场+返回地址
字符串的注意事项
- 比较
"hello"=="hello" //两个不相等,比较的是地址,要用strcmp进行比较
- 赋值
char ch[5]; ch="hello"; //error,而是strcpy(ch,"hello");
- 待续
1.2 练习
【例1.1】 最简单的C语言程序:
#include <stdio.h>//包含输入输出函数
int main()//返回int型,所以需要return 0;
{
printf("hello world!\n");//换行:‘\n’,printf表示输出
return 0;
}
【例1.2】求两个整数和
#include <stdio.h>
int main()
{
int a,b;
scanf("%d%d",&a,&b);//scanf()输入函数,除字符串都要加&
int sum=a+b;
printf("a+b=%d\n",sum);
return 0;
}
【例1.3】求两个整数的较大者
#include <stdio.h>
int max(int num1,int num2);
int main()
{
int a,b;
scanf("%d%d",&a,&b);
printf("%d\n",max(a,b));
return 0;
}
int max(int num1,int num2){
return num1>num2?num1:num2;
}
⛳️第二章 算法—程序的灵魂
2.1 基本知识
程序=算法+数据结构
算法的特性:
- 有穷性
- 确定性
- 有效性
- 零个或多个输入
- 一个或多个输出
循环结构:当型和直到型
- 当 型:先判断后执行
- 直到型:先执行后判断
2.2 练习
1. 判断是否是闰年(被4整除但不可被100整除 || 被400整除)
2. 1+1/2-1/3+1/4-...+1/n
【例2.1】计算1*2*3*4*5
#include <stdio.h>
int Mul(int n);//计算从1乘至n的积
int main(){
int n;
scanf("%d",&n);
printf("乘积为%d\n",Mul(n));
return 0;
}
int Mul(int n){//计算从1乘至n的积
int product=1;
for(int i=1;i<=n;i++){
product*=i;
}
return product;
}
【例2.2】略(文字表述)
【例2.3】判断是否是闰年(被4整除但不可被100整除 || 被400整除)
#include <stdio.h>
void RunYear(int n1,int n2);//判断n1至n2中属于闰年的年份并输出
int main()
{
int year1,year2;
scanf("%d%d",&year1,&year2);
RunYear(year1,year2);
return 0;
}
void RunYear(int n1,int n2){//判断n1至n2中属于闰年的年份并输出
int count=1;//限制输出格式
for(int i=n1;i<=n2;i++){
if((i%4==0 && i%100!=0) || (i%400==0)){
if(count%6==0){
printf("\n");
count=1;
}
count++;
printf("%d ",i);
}
}
}
【例2.4】1+1/2-1/3+1/4-...+1/n
#include <stdio.h>
double fun(int n){
if(n<=0) return -1;
if(n==1) return 1;
double sum=1;
int sign=1;
for(int i=2;i<=n;i++){
sum+=sign*(1.0/i);
sign=-1*sign;
}
return sum;
}
int main(){
int n;
scanf("%d",&n);
double add=fun(n);
printf("%.2f",add);
return 0;
}
【例2.5】判断一个大于等于3的正整数是否为素数(只能被1和它本身整除)
素数是大于1的自然数,并且只有两个正因子(1和它本身)。1不是素数,2是素数。
#include <stdio.h>
int PrimeNum(int n);//n是素数返回1,否则-1
int main()
{
int n;
scanf("%d",&n);
if(PrimeNum(n)>0){
printf("%d是素数\n",n);
}
else printf("%d不是素数\n",n);
return 0;
}
int PrimeNum(int n){//n是素数返回1,否则-1
for(int i=2;i<=n;i++){
if(i==n) {return 1;break;}
if(n%i==0){return -1;break;}
}
return 0;
}
⛳️第三章 顺序结构程序设计
3.1 常量和变量
1.常量
- 整型常量:如1000,-324
- 实型常量:12.442,0.145E-25
- 字符常量:'a','\n'
- 字符串常量:"hello"
- 符号常量:#define PI 3.14
2.变量
3.常变量
4.标识符:字母、数字(不可’首位)或下划线
3.2 数据类型
3.3 基本运算符
算术运算符:
用于各类数值运算。包括加(+),减(-),乘(*),除(/),求余(%),自增(++),自减(--)。
关系运算符:
用于比较运算。包括大于(>),小于(<),等于(=),大于等于(>=),小于等于(<=)和不等于(!=)。
逻辑运算符:
用于逻辑运算。包括与(&&),或(||),非(!)。
位操作运算符:
参与运算的量,按照二进制位进行运算。包括位与(&),位或(|),位非(-),位异或(^),左移(<<),右移(>>)。
赋值运算符:
用于赋值运算,分为简单赋值(=),符合算数赋值(+=,-=,*=,/=,%=)和复合位运算符(&=,|=,^=,>>=,<<=)三类。复合位运算符在基础中不太常用,常用的为前两类。
条件运算符:
也是三目运算符,用于条件求值(?:)。
逗号运算符:
用于把若干表达式组合成一个表达式(,)。
指针运算符:
用于取内容(*)和取地址(&)。
求字节运算符:
用于计算数据类型所占的字节数(sizeof)。
特殊运算符:
有括号(),下标[],成员(-->,.)等
#include <stdio.h>
int main()
{
printf("10%%5=%d 5%%10=%d",10%5,5%10);//输出:10%5=0 5%10=5
return 0;
}
3.4 输入输出
scanf:
- int/short/long:%d
- double:%lf %f 都可以
- float:%f
- char:%c
- string:%s 不用'&', 如:scanf("%s",str); 而非scanf("%s",&str);
printf:
- int/short/long:%d
- double:%lf %f 都可以
- float:%m,nf【右对齐】 %-m,nf【左对齐】, 共占m列,带n位小数
- char:%c
- string:%s
- 输出%:%%
putchar(参数):输出单个字符
getchar():输入单个字符
⛳️第四章 选择结构程序设计
选择语句:
- if-else else与离它最近的 if 配对
- switch
#include <stdio.h> int main() { int choice; printf("Enter a number (1-3): "); scanf("%d", &choice); switch (choice) { case 1: printf("You chose option 1.\n"); break; case 2: printf("You chose option 2.\n"); break; case 3: printf("You chose option 3.\n"); break; default: printf("Invalid choice.\n"); } return 0; }
switch语句的注意事项:
每个
case
后要加break
: 在switch
语句的每个case
块结束后都要加上break
语句,以避免穿透到下一个case
。如果省略break
,程序会继续执行后面的case
块,直到遇到break
或者switch
语句结束。
switch
表达式的类型:switch
表达式的类型必须是整数类型+字符型(int
、char
、enum
等)。不支持浮点数或字符串作为switch
的表达式。
case
中的常量表达式:case
标签中的值必须是常量。
default
语句(可省略):default
语句通常放在switch
的最后,用于处理未匹配到任何case
的情况。然而,它也可以放在任何位置。条件表达式:表达式1?表达式2:表达式3;
示例:
max=a>b? a:b; //等价于 if(a>b) max=a; else max=b;
⛳️第五章 循环结构程序设计
循环语句:
- while(表达式) 语句 当型
- do 语句 while(表达式) 直到型
- for(表达式1;表达式2;表达式3) 语句 表达式1、2、3都分别可省,也可以都省
注意:
break:终止循环
continue:结束本次循环,进入下次循环 for的表达式3如i++会被执行
⛳️第六章 利用数组处理批量数据
6.1 基本知识
数组特点:
- 有序数据的集合,属于同一数据类型
- 下标从0开始
- 提前开辟固定大小
- 作为参数传递实际上传递的是地址,故在函数中修改原数组也会改变
C语言无string和bool类型
注意:
1.
int a[10];//范围是a[0]至a[9],不存在a[10]
2.
//需要提前开辟大小 int a[10];//ok int n=10; int a[n];//error!
6.2 一维数组 的定义+初始化
int a[10];//a[0]至a[9],共10个
int a[10]={1,2};//整型后面默认0,字符型默认'\0',指针默认NULL
int a[0]={0};//全0
int a[0]=123;//类推
6.3 二维数组 的定义+初始化
float a[3][5];
float a[3][5]={{1,2},{3,4,5},{}};
float a[2][2]={0,1,2,3};
6.4 字符数组 的定义+初始化
char c[10]={'h','e','l','l','o'};///长度10
char c[]={'h','e','l','l','o'};//长度5
char c[]={"hello"};//长度6
#include <stdio.h>
double CountNum(int n);
int main(){
char ch1[]={'h','e','l','l','o'};//5
char ch2[]="hello";//5+1
printf("ch1.len=%d ch2.len=%d\n",sizeof(ch1),sizeof(ch2));
char c1[]={"I am happy"};//10+1
char c2[]="I am happy";//10+1
printf("c1.len=%d c2.len=%d\n",sizeof(c1),sizeof(c2));
char c3[]={'I',' ','a','m',' ','h','a','p','p','y','\0'};//10+1
char c4[]={'I',' ','a','m',' ','h','a','p','p','y'};//10
printf("c3.len=%d c4.len=%d\n",sizeof(c3),sizeof(c4));
return 0;
}
⛳️第七章 用函数实现模块化设计
7.1 基本知识
函数调用:
- 函数间可以相互调用,除了main()函数【由OS调用】
- 调用之前需要先声明
函数类型:
- 需要return 值,则函数类型决定返回值类型,如return的imyCar变量类型和int fuc(xx)保持一致
- 无需return 值 则定义函数类型为void
数组做函数参数示例:
void fuc(int a[],int n){...} //通过 n 指定数组大小
void fuc(int *a,int n){...} //等价
- int fuc(int a[10]){...} //直接指定数组大小,其实 10 不起作用
- int fuc(int a[]){...} //不指定数组大小
内部函数和外部函数:
- 内部函数(static):只在当前文件中可见的函数,不能被其他文件中的函数调用。
- 外部函数(extern【默认】):是指可以被其他文件中的函数调用的函数。
数组做参数在函数内变化,则原数组值也会变。示例:
#include <stdio.h>
void inv(int a[],int n);
int main()
{
int a[5]={1,2,3,4,5};
inv(a,5);
for(int i=0;i<5;i++){
printf("%d ",a[i]);
}
return 0;
}
void inv(int a[],int n)
{
for(int i=0;i<n-1;i++){
for(int j=0;j<n-1-i;j++){
if(a[j]<a[j+1]){
int t=a[j];
a[j]=a[j+1];
a[j+1]=t;
}
}
}
}
⛳️第八章 善于利用指针
8.1 基本知识
基本概念:
- 指针就是地址=数据类型+存储编号 int a1;float a2;//前后的地址不同
int a; int * ptr; //指针变量是ptr,类型是int * ptr=&a;
- 指针的强制类型转换需要加上*,如
int intValue = 42; int *intPointer = &intValue; // 强制转换int指针为float指针 float *floatPointer = (float*)intPointer;
指针数组:每个元素存放一个地址,应用场景:指向若干字符串+做main()的形参
int *p[4]; //指针数组 int (*p)[4]; //指针变量:指向一维数组
const指针用法详见:define与const关键字的多种用法-CSDN博客
8.2 练习
【例8.2】看懂即可
/*地址变,值不变*/
#include <stdio.h>
int main()
{
int a,b;//5 3
scanf("%d%d",&a,&b);
int *ptr1=&a;
int *ptr2=&b;
if(a>b){
int t;t=ptr1;ptr1=ptr2;ptr2=ptr1;
}
printf("a=%d b=%d\n",a,b);//5 3
printf("*ptr1=%d *ptr2=%d",*ptr1,*ptr2);//3 5
return 0;
}
【例8.3】看懂即可
#include <stdio.h>
void swap(int *ptr1,int *ptr2);
int main()
{
int a,b;//5 3
scanf("%d%d",&a,&b);
int *ptr1=&a;
int *ptr2=&b;
if(a>b){
swap(ptr1,ptr2); //而不是swap(*ptr1,*ptr2);
}
printf("a=%d b=%d\n",a,b);//3 5
printf("*ptr1=%d *ptr2=%d",*ptr1,*ptr2);//3 5
return 0;
}
void swap(int *ptr1,int *ptr2){//地址不变;a,b值交换
int t=*ptr1;
*ptr1=*ptr2;
*ptr2=t;
}
/*
void swap(int *ptr1,int *ptr2){//地址变;a,b值不变
int *t=ptr1;
ptr1=ptr2;
ptr2=t;
}*/
/*
void swap(int ptr1,int ptr2){//没有用
int t=ptr1;
ptr1=ptr2;
ptr2=t;
}*/
8.3 指针指向数组+字符串
一维数组:
//一维数组 int a[5]={1,2,3,4,5}; int *ptr1=&a[0];//等价于int *ptr1=a;
&a[ i ]等 价于 p+i 等价于a+i
*(p+i) 等价于 *(a+i)等价于a[ i ]
scanf("%d",p);
多维数组:
- &a[0][i] 等价于 * (a+0)+i 等价于 a[0]+i
- a[0][i] 等价于 *(* (a+0)+i) 等价于 *(a[0]+i)
- 举例:a[1][2]表示为 *(*(a+1)+2)而非*(a+1+2),后者表示a[3]
int a[3][4]={1,2,3,0,0,0,0,0,0}; int *p=a[0]; int (*p)[4]=a;//指针变量p指向 包含4个元素的一维数组 //(*p)[0],(*p)[1],(*p)[2],(*p)[3]分别代表a[0]至a[3]
字符串
对于字符指针指向字符数组,需要注意使用scanf需要加&,后续定义或输出都不加*和&。
对于字符指针指向字符串,需要注意使用scanf不要加&,后续定义加&,输出不加*。
字符指针指向字符数组
char *str="hello world"; //等价于 char *str; str="hello world";//而不是* str="hello world"; printf("%s",str);//而不是printf("%s",* str);
#include <stdio.h> int main(){ /*简单示例*/ char ch[100]; scanf("%s",&ch);//字符串则不用加 & char *str; str = ch; // 而不是 str =& ch; printf("%s\n", str); return 0; }
字符指针指向字符串【C++程序,C语言不提供string类型】
#include <iostream> #include <cstring> using namespace std; int main(){ /*简单示例*/ string s; getline(cin,s);//字符串使用scanf 则不用加 & char *str; str = &s[0]; // 而不是 str =& ch; printf("%s\n", str); return 0; } /* hellow djck hellow djck */
【例8.8】数组翻转
#include <stdio.h>
void inv(int a[],int n);//数组翻转
int main()
{
int a[6]={1,2,3,4,5,6};
inv(a,6);
for(int i=0;i<6;i++){
printf("%d ",a[i]);//6 5 4 3 2 1
}
return 0;
}
//等价于void inv(int *a,int n){
void inv(int a[],int n){//数组翻转
for(int i=0;i<n/2;i++){
int j=n-i-1;
int t=a[i];
a[i]=a[j];
a[j]=t;
}
}
8.4 函数指针
8.5 malloc、free动态内存分配
局部变量存放栈区,而malloc开辟的数据存放在堆区。
malloc
和free
是 C 语言中用于动态内存分配和释放的函数需要导入#include <stdlib.h>
头文件int *a=(int *)malloc(sizeof(int));//变量 int *arr=(int*)malloc(sizeof(int) * 5);//数组
- 注意:使用
malloc
分配内存后,一定要使用free
来释放,以防止内存泄漏。
示例:
#include <stdio.h>
#include <stdlib.h>//调用maloc和free
int main() {
// 分配一个可以存储 5 个整数的内存块
int *arr=(int*)malloc(5 * sizeof(int));
// 使用动态分配的内存
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
// 打印数组元素
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
// 释放动态分配的内存
free(arr);
int *a=(int *)malloc(sizeof(int));
a=5;
printf("a=%d\n",a);
free(a);
return 0;
}
8.6 void *p (无类型指针)
注意在使用
void *
指针时,为了正确解引用指针并获取其指向的值,需要在使用前进行类型转换。无法进行指针运算malloc返回的就是void *指针
下面代码中使用了 (int*)
、(double*)
和 (char*)
分别进行了类型转换。
#include <stdio.h>
int main() {
// 声明一个 void 指针
void *p;
// 声明一些变量
int num = 10;
double pi = 3.14159;
char letter = 'A';
// 将 void 指针指向不同类型的变量
p = #
printf("整数变量的值:%d\n", *((int*)p));
p = π
printf("双精度浮点数变量的值:%lf\n", *((double*)p));
p = &letter;
printf("字符变量的值:%c\n", *((char*)p));
return 0;
}
⛳️第九章 用户自己建立数据类型
9.1 结构体struct
作用:不同数据类型的组合型,结构体长度=各个成员变量长度累加
结构体类型声明形式
struct Student
{成员1;成员2;...
};//也可在此定义}s1,s2;
后续定义示例 struct Student s1,s2;
typedef struct Student
{成员1;成员2;...
}Student;
后续定义示例 Student s1,s2;
结构体指针:struct Student *ptr=&s1;
输出两种方式:ptr->name (*ptr).name 等价 s1.name
示例:
#include <stdio.h>
#include <string.h>
// 定义学生结构体
struct Student {
int studentID;
char name[50];
int age;
float GPA;
};
int main() {
// 声明一个学生结构体变量
struct Student student1;
// 初始化结构体成员
student1.studentID = 12345;
strcpy(student1.name, "John Doe");//不可student1.name="John Doe";
student1.age = 20;
student1.GPA = 3.8;
// 打印学生信息
printf("Student ID: %d\n", student1.studentID);
printf("Name: %s\n", student1.name);
printf("Age: %d\n", student1.age);
printf("GPA: %.2f\n", student1.GPA);
// 声明一个指向结构体的指针
struct Student *ptrStudent;
// 让指针指向学生结构体变量
ptrStudent = &student1;
// 通过指针访问结构体成员
printf("\nVia Pointer:\n");
printf("Student ID: %d %d\n", ptrStudent->studentID,(*ptrStudent).studentID);
printf("Name: %s %s\n", ptrStudent->name,(*ptrStudent).name);
printf("Age: %d %d\n", ptrStudent->age,(*ptrStudent).age);
printf("GPA: %.2f %.2f\n", ptrStudent->GPA,(*ptrStudent).GPA);
return 0;
}
9.2 共用体union
作用:同一段存储单元存放不同类型的变量,共用体长度=max(各个成员变量的存储长度)
共用体类型声明形式
union Data
{成员1;成员2;...
};//也可在此定义}d1,d2;
后续定义示例 union Data d1,d2;
typedef union Data
{成员1;成员2;...
}Data;
后续定义示例 Data d1,d2;
注意:同一时间只能存在一个成员。
9.3 枚举类型enum
作用:一个变量只有几种可能的值
枚举型声明形式
enum Weekday {
枚举常量1,枚举常量2,...
};//也可在此定义}s1,s2;后续定义示例 enum Weekday s1,s2;
typedef enum Weekday {
枚举常量1,枚举常量2,...
}Weekday;后续定义示例 Weekday d1,d2;
注意:枚举元素按常量处理,不能赋值。默认从0开始赋值。
#include <stdio.h>
// 定义一个枚举类型
enum Weekday {
SUNDAY, // 0
MONDAY, // 1
TUESDAY, // 2
WEDNESDAY, // 3
THURSDAY, // 4
FRIDAY, // 5
SATURDAY // 6
};
int main() {
// 声明一个枚举变量
enum Weekday today;
// 给枚举变量赋值
today = WEDNESDAY;
// 使用枚举变量
switch (today) {
case SUNDAY:
printf("It's a lazy day!\n");
break;
case MONDAY:
printf("Start of the workweek\n");
break;
case WEDNESDAY:
printf("Halfway through the week!\n");
break;
default:
printf("It's a regular day\n");
}
return 0;
}
📝总结
嘘,听说有一位C语言大师突破了次元壁,成功通关了编程的炼金之路!从入门到进阶,你的代码之旅如同编程宇宙的星空,熠熠生辉。你不仅掌握了代码的秘法,更诠释了编程的独特魔力。每一次Debug都是一场魔法修炼,每一行代码都是一篇炫目的咒语。恭喜你,编程冒险家,你已经成为这片代码大陆的传奇英雄。未来等着你用键盘书写更加壮丽的代码史
诗,展开属于你的数字冒险。继续释放编码魔法,让代码的光芒照亮前行的路途!