目录
这是我的第一篇博客,总结了我大一初学C语言的过程和心得,后续会继续发表学习编程的其他体会和笔记。(欢迎留言补充)
这是我的第一篇博客,总结了我大一初学C语言的过程和心得,后续会继续发表学习编程的其他体会和笔记。(欢迎留言补充)
初识C语言
这里会介绍学习C语言入门的整个逻辑框架,让你有一个清晰的学习路径。
整个C语言程序要经过编辑、编译、连接、运行这四个步骤。
学习C语言要学习它的
1.数据类型
2.变量和常量
3.字符串+转义字符+注释
4.选择语句
5.循环语句
6.函数
7.数组
8.操作符
9.常见关键字
10.define定义常量和宏
11.指针
12.结构体+链表
首先就先了解C语言的数据类型
数据类型
这一张图就基本涵盖了C语言所有的数据类型
当你第一次敲代码的时候肯定是敲了hello world
#include <stdio.h>
int main(void)
{
printf("hello world\n");
return 0;
}
这是最简单的一个C语言程序了,让我们来分析这个最简单的C语言程序
#include <stdio.h> ------>这是头文件 ,它是预处理指令
int main(void) ----->这是主函数,里面的代码用{}括起来,程序从主函数开始运行。int 表示返回类型 ,()里面的void表示无参数,这个在函数那里会仔细说明。
printf()这是我们上面写的头文件里面带的输出函数,C语言本身是没有输入输出函数,所以要用其他的函数要声明头文件才可使用。
再回到我们这里说的数据类型。为什么会有很多数据类型呢?
这是因为C语言不同的数据类型能够较为准确的表示和存储各种不同类型的数据;其次数据类型的多样性有助于提高程序的效率;再者,不同的数据类型可以增强程序的可读性和可维护性;最后多种数据类型也为处理复杂的数据结构和算法提供了基础。
让我们再看一个简单的代码
#include <stdio.h>
int main(void)
{
int a,b;
a = 3 , b = 4;
int c = a+b;
printf("%d",c);
return 0;
}
使用C语言程序来进行简单的数学运算,那么就要用到整形类型,这里用到int类型,也就是整数类型,这里的 = 不是我们数学里相等,而是把右边的值赋值给左边的变量。如果你想进行运算,肯定要声明变量并且声明它是什么类型的。
这是三个基本大类的数据类型:整形类型,浮点类型,字符类型。
往往这些类型还要跟格式符对应
以下是 C 语言中常见的格式符及其含义:
1. %d :用于输出有符号十进制整数。
2. %u :用于输出无符号十进制整数。
3. %f :用于输出浮点数(包括单精度和双精度),默认保留 6 位小数。
4. %e 或 %E :以科学计数法形式输出浮点数。
5. %g 或 %G :根据数值的大小,自动选择 %f 或 %e 格式输出。
6. %c :用于输出单个字符。
7. %s :用于输出字符串。
8. %p :用于输出指针的值。
9. %o :用于输出无符号八进制整数。
10. %x 或 %X :用于输出无符号十六进制整数, %x 以小写字母输出, %X 以大写字母输出。
最后还有就是要记住ASCII码表中字母对应的ASCII码值。这个可以在网上自行查找,对之后的学习和写代码有很大帮助。
分支语句和循环语句
什么是语句?
C语言可分为以下五类:
1.表达式语句
2.函数调用语句
3.控制语句
4.空语句
5.符号语句
C语言有九种控制语句
可分为以下三类:
1.条件语句:if语句、switch语句
2.循环语句:do while语句、while语句、for语句
3.转向语句:break语句、goto语句、continue语句、return语句
if语句——
if(表达式)
{
语句;
}else{
语句;
}
例:
#include <stdio.h>
int main(void)
{
int a,b;
a=0,b=4;
if(a==0)
{
printf("很好");
}else{
printf("不好");
}
return 0;
}
提问:a==0和a=0有啥区别? 如果把a==0换成a=0结果又会是什么?
switch语句(这种语句在特定条件下使用会很轻松)
比如用这个语句做一个成绩评级的程序
#include<stdio.h>
int main(void)
{
int score;
score = 10 ;
switch (score)
{
case 10:
printf("A\n");
case 9:
printf("A\n");
break;
case 8:
printf("B\n");
break;
case 7:
printf("C\n");
break;
case 6:
printf("D\n");
break;
default:
printf("E\n");
break;
}
return 0;
}
提问:这个输出结果是什么? 如果在case 9:的上一行加一个break;结果又是什么?
for语句(这是循环语句)
for(初识赋值;条件;改变条件)
{
语句;
}
#include<stdio.h>
int main(void)
{
int i = 0;
for (i = 0; i < 100; i++)
{
printf("你好\n");
}
return 0;
}
这个运行逻辑先进行赋值i=0,然后进行条件比较如果i<100,则进入循环反之不进入循环。
然后每次循环一次,执行最右边的条件然后再进行中间的条件比较,符合的话再一次执行, 不符合跳出循环。
while语句
while(条件)
{
语句;
}
#include<stdio.h>
int main(void)
{
int i = 0;
while(i<10)
{
printf("我要学好C语言\n");
i++;
}
return 0;
}
这个运行逻辑是先判断i是否小于10,如果小于10,执行括号内的语句,每一次循环结束后,再一次比较i是否小于10,若i大于或等于10,循环结束。
do……while语句
do
{
语句
}while(条件)
这个跟while语句就一个不一样,do while是至少执行一次语句,while语句是先判断条件。
goto语句
慎用
goto语句是想跳到哪去
不能跨函数跳转
int main()
{
again:
printf("123");
printf("245");
goto again;
}
goto语言真正适用的场景:
跳出两层或多层循环
变量和常量
全局变量
全局变量是在函数外部定义的变量,其作用域从定义的位置开始,到整个程序结束。
全局变量的主要特点和优点包括:
1. 多个函数可以共享和访问全局变量,方便在不同函数之间传递数据,减少函数参数的传递。
2. 全局变量在程序的整个执行期间都存在,其值会被保留。
然而,使用全局变量也存在一些潜在的问题:
1. 可能导致程序的可读性和可维护性降低,因为全局变量可以在任何地方被修改,难以追踪和理解数据的变化。
2. 增加了程序模块之间的耦合性,不利于代码的模块化和可重用性。
3. 可能引发命名冲突,特别是在大型项目中。
局部变量
局部变量是在函数内部或代码块内部定义的变量。
局部变量具有以下特点:
1. 作用域仅限于定义它的函数或代码块内部。一旦函数执行完毕或代码块结束,局部变量就会被销毁,其所占用的内存也会被释放。
2. 每次进入函数或代码块时,局部变量都会重新创建并初始化(如果有初始化操作)。
3. 局部变量可以屏蔽在其外部定义的同名全局变量。
局部变量的优点包括:
1. 提高了代码的封装性和模块性,使得函数内部的变量不会影响到其他函数的运行,降低了函数之间的相互干扰。
2. 有助于减少变量名冲突的可能性。
常量
字面常量:数字、字符、字符串
const修饰常变量
const int a = 20; //这个在后续无法改变a的值
#define定义的标识符常量
#define max 100 //定义max为100(也用来赋值字符串,不能改)
枚举常量
enum color
{
red,
green,
blue //这个就是枚举常量,底层是0,1,2 …
};
enum color x = red ;//其实x == 0
字符串+转义字符+注释
字符串
char ch = 'w' //字符
字符串怎么被赋值呢?
char arr[] = "abcdefg"; //用数组赋值(后面也可以用指针赋值)
注意:字符串的结束标志是一个\0的转义字符,在计算字符串长度的时候\0是结束标志,不算作字符串内容。
arr[6]代表着这个数组只能存储后面赋值的六个单位。
转义字符
\? 在书写多个问号时,防止被解析为三字母词----??)
\' 用于表示字符常量’
\" 用于表示一个字符串内部双引号
\\ 用于表示一个反斜杠
\a 警告字符
\b 退格符
\f 进纸符
\n 换行
\r 回车
\t 水平制表符,相当于tab
\v 垂直制表符
\ddd ddd表示1-3个八进制数字
\xdd xdd表示2个十六进制数字,如\30 0 (ASCII码值,算作一个字符)
注释
// 是在每行代码后面
/* */ 这这两个*号中间的都算作注释
函数
返回类型 函数名 参数
int add (int x ,int y)
{
return z;
}
返回类型有void(无返回),int(返回整数),float(返回单精度浮点数),double(返回双精度浮点数),char(返回字符类型)
我这里介绍几个常用的函数以及如何定义一个函数并让它发挥作用
常见函数
printf() 输出函数 在<stdio.h> 里面
scanf()输入函数 在<stdio.h>里面
这里尤其注意:scanf函数有一个缓冲区,当你按下回车键的时候,你输入的内容被赋给变量了,但是\n回车还保留在缓冲区,所以当你下面使用到gets()函数做输入整个字符串的时候就会出现问题。
在这有可能同学会发现scanf()在编译器上可能会报错,不是你的代码错了,是编译器认为不安全,只需要在第一行添加#define _CRT_SECURE_NO_WARNINGS 即可
getchar() 在<stdio.h>
gets() 在<stdio.h> ,这个函数可以输入字符串
四大字符函数都在<string.h>
strlen() 用于测量字符串长度
strcpy( , ) 用于把右边的字符串赋值给左边的字符串
strcat( , )用于把右边的字符串添加到左边的字符串的结尾
strcmp( , ) 用于比较两个字符串的大小,若相等返回0,比较字符串大小就是一个个比较字符的ASCII码值
下面在<stdlib.h>
malloc 用于动态分配内存,在后面的链表会使用说明
srand(time(0))用于生成随机数种子,还有配套使用time()函数,time函数在<time.h>
rand() 用于生成随机数,想生成1-10的随机数那么 int a = rand()%10+1;
数组
一维数组
数组的定义
int arr[10] = {0,1,2,3,4,5,6,7,8,9}
double d[30] = {1.1,1.2};
char name[10]="你好" //中文占两个字符
二维数组
char ch[3][10] = {"chl","uio","yop"}; //可以存字符串元素
二维数组也可以看作矩阵,学了线性代数可以试着写一写。
数组名就相当于一个地址,代表着这个数组的地址。
注意:数组名不可以自增自减
操作符
算术操作符
+,-,*,/,%
移位操作符
>> (右移)(右移几位相当于除以2的几次方)
<< (左移)(右移几位相当于乘2的几次方)
位操作符
& 二进制对应的都为1才为1,否则为0
^ 二进制对应的不同为1,相同为0
| 二进制对应的有一个为1就为1,否则为0
赋值操作符
= += -= *= /= &= ^= |= >>= <<=
逻辑操作符
&&且
||或
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度
int arr[10] = { 0 };
sizeof(arr)== 40;
~ 对一个数按二进制位取反
--
++
关系操作符
>
>=
<=
!=
==
条件操作符
条件? 公式1:公式二 (若为真,执行公式1;若为假,执行公式2)
逗号表达式
从左到右依次计算,整个表达式的结果是最后一个表达式的结果
期中练习题
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main(void)
{
int i = 0;
printf("整数运算器(+、-、*)\n");
printf("=====================\n");
printf("| +:加法 |\n");
printf("| +:减法 |\n");
printf("| +:乘法 |\n");
printf("=====================\n");
srand(time(0));
int k = rand() % 10 + 1;
int a, b;
char c;
for (i = k; i >= 1; i--)
{
printf("您还可以正确计算%d次\n请输入表达式:", i);
scanf("%d%c%d", &a, &c, &b);
if (c != '+' && c != '-' && c != '*')
{
i++;
continue;
}
else {
if (c == '+')
printf("和为:%d\n", a + b);
if (c == '-')
printf("差为:%d\n", a - b);
if (c == '*')
printf("积为:%d\n", a * b);
}
}
return 0;
}
这个读者可以看看这个代码是干嘛的
二分法
求100——200之间的质数和
1.首先我们要知道如何判断一个数是否为质数
2.求和
3.如何优化代码(二分法)
//#include <stdio.h>
//int main(void)
//{
// int sum = 0;
// int i = 0,j=0;
// int sign = 0;
// for (i = 100; i <= 200; i++)
// {
// sign = 1;
// for (j = 2; j < i / 2; j++)
// {
// if (i % j == 0)
// sign = 0;
// }
//
// if (sign == 1)
// {
// sum += i;
// }
// }
//
// printf("%d", sum);
//}
冒泡排序(从小到大)
#include <stdio.h>
int main(void)
{
int arr[10] = { 0 };
int sum = 0;
for (int i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);
}
for (int i = 0; i < 10; i++)
{
for (int j = i; j <= 10; j++)
{
if (arr[j] < arr[i])
{
int temp = arr[j];
arr[j] = arr[i];
arr[i] = temp;
}
}
}
}
常见关键字
auto | break | const | continue | default |
case | if | else | for | extern |
char | int | long | float | double |
short | long long | enum | struct结构体 | union联合体 |
goto跳转 | register寄存器 | return | signed有符号的 | unsigned |
sizeof | static静态的 | switch | typedef类型重命名 | void |
volatile | while | do |
关键字之typedef
typedef顾名思义就是类型定义
typedef unsigned int uint;
int main()
{
unsigned int num = 0;
uint num =1; //改变num为1
return 0;
}
关键字之static
1.修饰局部变量
2.修饰全局变量
3.修饰函数
void test()
{
int a =1; //如果是static int a = 1;打印结果就是2~11,想当于就赋值一次。
a++;
printf("%d",a);
}
int main()
{
int i =0;
while(i<10)
{
test();
i++;
}
return 0;
}
栈区
栈区是内存模型中的一个区域,主要用于存放函数的参数值、局部变量等。
堆区
一般由程序员分配和释放,堆区的内存分配不是连续的。要使用malloc、new等分配内存的函数来申请堆内存。
静态区
static修饰局部变量改变了变量存放位置
2.修饰全局变量
static修饰全局变量的时候,这个全局变量的外部链接属性就变成了内部链接属性。其他源文件就不能再使用这个全局变量。
例如:
text.c
int salary = 2023;
past.c
extern int salary;
int main()
{
printf("%d\n",salary);
return 0;
}
如果在text.c中改为static int salary = 2023;
那么past.c就会报错。
3.修饰函数
函数是具有外部链接属性的,但是被static修饰的时候,外部链接属性就变成了内部链接属性,其他源文件就无法使用
extern int Add(int x,inty); //加上static就无法使用
关键字之register
register --寄存器
电脑上的存储设备都有啥?
寄存器(集成到CPU)
高速缓存(cache)
内存
int main()
{
register int num = 3; //建议:3存放在寄存中,能更快被调用。只是建议,最终还要看编译器决定
return 0;
}
#define定义常量和宏
#define定义标识符常量
#define NUM 100 //可以正常赋值
#define定义宏
结构:#define ADD(x,y) ((x) + (y)) //一步就定义好简单“函数”
但是不能当作函数,宏就是只有一个返回值,没有函数的语句。
注意:一定要加(),不加()可能会发生改变
比如:
#define F 5+3
#define X 7
int a = F * X;
这个先预处理把F和X都直接替换成后面的变成 5+3*7,结果就是26.
指针
1.内存
计算机的运行都是在内存中进行的
内存被分成若干个内存单元,每个内存单元都有一个地址
int* p = &a; //p就是指针变量,地址也被称为指针
这个*前面的int是指向a是int类型
*p = 20 //解引用操作符,意思就是通过p中存放的地址,找到p所指向的对象,*p就是p指向的对象。
指针大小
//不管是什么类型的指针,都是在创建指针变量
//指针变量是用来存放地址的
//指针变量的大小取决于一个地址存放的时候需要多大的空间
//32位机器上的地址:32bit位 - 4byte,所以指针变量的大小是4个字节
//64位机器上的地址:64bit位 - 8byte,所以指针变量的大小是8个字节
指针一般会跟定义函数和数组相结合一起
比如:我想编写一个函数使得放进去的两个参数交换值
void change(int a,int b)
{
int temp;
temp = b;
b = a;
a = temp;
}
但是这个函数并不能交换成功
因为这个int a和int b是形参,是实参的临时拷贝,并不能代表实参,所以这个函数实现的根本就没用。
要实现的话,需要使用到指针
void change(int* a, int* b)
{
int temp;
temp = *b;
*b = *a;
*a = temp;
}
传入a和b的地址然后用地址解引来交换值
指针数组元素
int arr[10] = {0};
要想存储整个数组的地址可以 in(*p)[10] ;
*(arr+n) 解引数组元素 (n<10)
结构体
C语言就给了自定义类型的能力自定义类型的能力,自定义类型中有一种叫:结构体 struct
描述某某
struct STU
{
//成员
char name[20];
int age;
char sex[10];
char tele[12];
};
注意:在花括号后面还要加一个;
void print(struct STU *x)//这里必须放的是地址也就是指针,因为指针变量包含很多信息
{
printf("%s %d %s %s\n",x->name ,x->age, x->sex, x-> tele);
}
int main()
{
struct STU s = {"张三", 18, "男","5201314666"};
//结构体对象.成员名
printf("%s %d %s %s\n",s.name ,s.age, s.sex, s.tele);
//结构体指针变量->成员名
return 0;
}
这里是基础结构体
结构体还有一个特殊用法就是
链表
例题:
问题描述:
给定一个头结点为head的非空单链表,返回链表的中间结点。如果有两个中间节点,则返回第二个中间结点。
程序输入:
1 2 3 4 5
程序输出:
3
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
typedef struct Node
{
int data;
struct Node* next; //一个结构体还能存入另一个结构体指针变量,就是链表
}Node;
//这个定义一个结构体类型,并且用typedef把struct Node 变为 Node
int main(void)
{
Node* head; //这个是结构体指针变量
Node* h = NULL;
Node* p, * pre;
int i, n, judge = 1;
int count = 0;
scanf("%d", &n);
head = (Node*)malloc(sizeof(Node)); //动态分配一个地址给head
p = (Node*)malloc(sizeof(Node)); //动态分配给一个地址给p
head->data = n; //并且在head这个结构体指针变量中存入data,给他赋值
head->next = p; //在head这个结构体指针变量中 存入 另一个结构体指针变量
pre = h;
h = p; //把分配的的结构体指针变量赋给h,方便后续增删数据
count++;
if (getchar() == '\n') // 这一步是按题目要求判断是不是就一个data,特殊情况
{
pre->next = NULL;
judge = 0;
}
while (judge == 1)
{
scanf("%d", &n);
p = (Node*)malloc(sizeof(Node)); //再分配一个新的地址
h->data = n; //h就是刚才的结构体指针变量,再用它添加数据,因为地址不会改变
h->next = p; //跟上面一样添加一个新的结构体指针变量
pre = h; //相当于保留这一整个的地址,方便后续判断最后的指针是不是NULL
h = p;
count++;
if (getchar() == '\n') //这一步还是判断是不是最后一个数据了,在末尾添加NULL,跳出循环
{
pre->next = NULL;
break;
}
}
//创建好了一个链表,依次指向
for (i = 0; i < count; i++)
{
if (i == count / 2)
{
printf("%d", head->data);
break;
}
head = head->next;
} //这个for循环就是输出中间结点
return 0;
}