初级阶段(C语言入门)
一、课程导学、编程环境搭建(含安装包)
1.3 Windows的CLion开发环境安装
C语言之父——汤姆逊和丹尼斯里奇
安装MinGW编译器
安装CLion开发环境
点击 New Project 后,弹出下面窗口,图中的项目存储路径与项目名必须是英文的,不可含
有中文,如果自己的用户名是中文的,可以在 D 盘新建一个文件夹,放项目!
直接把压缩包拖进去
报错
1.5向日葵的安装
1.6什么是程序-编译与调试
程序的作用是完成某种计算
新建代码及编译运行
程序的编译过程及项目位置
!断点调试
二、数据的类型、数据的输入输出
2.1数据类型-常量-变量(整型-浮点-字符)
1.数据类型
2.常量
常量是指在程序运行过程中,其值不发生变化的量。常量又可以分为整型、实型(也称浮点型)、字符型和字符串型。
3.变量
变量代表内存中具有特定属性的一个存储单元,它用来存放数据,即变量的值。这些值在程序的执行过程中是可以改变的。
变量的命名规定:C语言规定标识符只能由字母、数字和下画线三种字符组成,并且第一个字符必须为字母或下画线。
4.整型数据
4.1符号常量
4.2整型变量
5.浮点型常量
5.1浮点型常量
5.2浮点型变量
通过float f来定义浮点变量,f占用4个字节的空间
6.字符型数据
6.1字符型常量
用单引号括起来的一个字符是字符型常量,且只能包含一个字符。
6.2字符数据在内存中的存储形式及其使用方法
7.字符串型常量
c语言中没有字符串变量
字符串常量是由一对双引号括起来的字符序列。
注意:'a'是字符型常量 "a"是字符串型常量,二者是不同的
8.ASCII表
2.2混合运算及printf的使用
1.强制类型转换
2.printf函数讲解
printf可以输出各种类型的数据,包括整型、浮点型、字符型、字符串型等。
原理(了解就行):printf函数将这些类型的数据格式化为字符串后,放入标准输出缓冲区,然后将结果显示到屏幕上
printf函数需要头文件声明
2.3整型常量的不同进制转换
十进制转换二进制:除2取余
为什么i的值是0x0000007b,显示结果却是7b000000呢?
原因是英特尔的CPU采用了小端方式进行数据存储,因此低位在前、高位在后。
2.4scanf读取标准输入
1.scanf函数的原理
c语言未提供输入/输出关键字,其输入和输出是通过标准函数库来实现的。c语言通过scanf函数读取键盘输入,键盘输入又称为标准输入。当scanf函数读取标准输入时,如果还没有输入任何内容,那么scanf函数会被卡主住(专业术语为阻塞)。
为什么第二个scanf不会阻塞呢?
如何避免这个问题呢?
为什么这个时候有两次阻塞呢?为什么此时不用fflush?
scanf会阻塞,是因为标准输入缓冲区是空的
整型数和浮点数都会忽略
2.多种数据类型混合输入
三、运算符与表达式
3.1上节课作业讲解
代码路径不能含有中文
printf打印输出不要输出中文,用纯英文,否则会造成乱码
3.2算数运算符和关系运算符
1.运算符分类
2.算术运算符及算术表达式
乘、除、取余运算符的优先级高于加减运算符,取余只能用于整型数
3.关系运算符与关系表达式
c语言中0值代表假,非0值即为真。
3<a<10从左往右运算,输入-2时,3<a的值为0,而0<10,因此输出第一个结果。
正确写法:
4 运算符优先级表
3.3逻辑运算符与赋值运算符,求字节运算符
1.逻辑运算符与逻辑表达式
短路运算
2.赋值运算符
赋值符号左边为左值,右边为右值
错误示范:
3.求字节运算符sizeof
(忘保存了,再写一遍吧,哭死o(╥﹏╥)o)
sizeof不是函数而是运算符 ,用于求常量或变量所占的空间大小
本节课OJ
课时3作业1
#include <stdio.h>
//判断某个年份是不是闰年,如果是闰年,请输出“yes”,否则请输出“no”
int main() {
int a;
scanf("%d",&a);
if(a%4==0&&a%100!=0||a%400==0)
{
printf("yes");
}else{
printf("no");
}
return 0;
}
课时3作业2
#include <stdio.h>
//读取一个整型数,字符,浮点数,分别到变量i,j,k中,然后将i,j,k直接相加并输出,小数点后保留两位小数,不用考虑输入的浮点数的小数部分超过了两位。
int main() {
int i;
char j;
float k;
scanf("%d %c%f",&i,&j,&k);
printf("%5.2f",i+j+k);
return 0;
}
讲解:
%0.2f不限制浮点数输出整体的长度
OJ题读取输入的策略:
第一种情况:读取一行,写一个scanf即可
第二种情况:读取多行,假如三行,for循环3次
注意scanf中不要写\n
记住运算符的优先级,尽量不写太多括号
四、选择、循环
4.2 选择if-else讲解
1.关系表达式与逻辑表达式
算术运算符的优先级高于关系运算符、关系运算符的优先级高于逻辑与和逻辑或运算符
双目运算符:
左操作数+右操作数
单目运算符:
!操作数 逻辑非就是单目运算符
5>3&&8<4-!0
2.if-else语句
加大括号 else不管格式对齐谁,都遵循就近原则
4.3 循环while,for讲解,continue,break讲解
while循环
while()后加分号就会死循环
在循环体内没有让while判断表达式趋近于假的操作,死循环
2.for循环
for和while可以相互转换,for循环不容易忘记初始化
3.continue语句
while语句使用continue时要注意有没有把i++忽略
4break语句
5循环嵌套
4.4 本节课OJ作业
输入一个整型数,判断是否是对称数,如果是,输出yes,否则输出no,不用考虑这个整型数过大,int类型存不下,不用考虑负值;
例如 12321是对称数,输出yes,124421是对称数,输出yes,1231不是对称数,输出no
思路:逆置之后的数字和原来相同
我的答案:
#include <stdio.h>
int main() {
int a,b=0,c=0,number=0,n;
scanf("%d",&a);
n=a;
for(a;a>0;a=(a-b)/10)
{
b=a%10;
number=c+b;
c=number*10;
// printf("a=%d,b=%d,c=%d,number=%d\n",a,b,c,number);
}
if(number==n)
{
printf("yes");
}else{
printf("no");
}
return 0;
}
老师的答案:
#include <stdio.h>
//对称数的判断,老师的答案
int main() {
int a,b=0,backup_a;//可以用backup_a来备份a
scanf("%d",&a);//读取一个整型数
backup_a=a;
while (a)
{
b=b*10+a%10;
a=a/10;
}
if(backup_a==b)
{
printf("yes");
}else{
printf("no");
}
return 0;
}
虽然我的答案也AC了,但是我写的有点拧巴,还有问题
问题一:a=(a-b)/10这属实多余,a是整型,计算出来的一定是个整数,不用减b
问题二:循环体里的式子写的不够简洁
利用while或者for循环计算n!的值。提示:n!=1*2*3…*n
#include <stdio.h>
int main() {
int n,i=1,a=1;
scanf("%d",&n);
for(i=1;i<=n;i++)
{
a=a*i;
}
printf("%d",a);
return 0;
}
某人想将手中的一张面值100元的人民币换成10元、5元、2元和1元面值的票子。要求换正好40张,且每种票子至少一张。问:有几种换法?
#include <stdio.h>
int main() {
int a,b,c,s,n=0;
for(a=1;a<=10;a++)
{
for(b=1;b<=20;b++)
{
for(c=1;c<=50;c++)
{
s=a+b+c;
if(s<40&&10*a+5*b+2*c+40-s==100)
{
n=n+1;
}
}
}
}
printf("%d",n);
return 0;
}
我的问题:
s这个常量完全可以不定义
1元的钞票也得大于1张,2元的钞票最多只能到40
老师的答案:
#include <stdio.h>
int main() {
int a,b,c,d,count=0;
for(a=1;a<=10;a++)
{
for(b=1;b<=20;b++)
{
for(c=1;c<=40;c++)
{
for (d=1;d<=40;d++)
{
if(a+b+c+d==40&&10*a+5*b+2*c+d==100)
{
count=count+1;
}
}
}
}
}
printf("%d",count);
return 0;
}
五、一维数组与字符数组
5.2一维数组
数组的定义
借助C语言提供的数组,通过一个符号来访问多个元素。
一维数组的定义格式为:类型说明符 数组名[常量表达式]
例如 int a[10];
一维数组在内存中的存储
5.3数组的访问越界与数组的传递
数组的访问越界
这里我有个疑问,为什么先给j赋值,在内存视图中反而是i的值在前面呢?
操作系统对内存中的每个位置也给予一个编号。
对于windows32位控制台应用程序来说,这个编号的范围是从0x00 00 00 00到0xFF FF FF FF,总计为2的32次方,大小为4G。这些编号称为地址
数组中需要注意的地方:编译器并不检查程序对数组下标的引用是否在数组的合法范围内。
访问越界危害:会改变其他变量的值
常见犯错例子:for循环中
数组的传递
灵活输出数组的值
将关键部分移动到子函数,为什么程序运行的不正确?
那应该怎么解决这个问题?
再定义一个变量length,将数组的长度传递过去
5.4 字符数组与scanf读取字符串
字符数组初始化及传递
初始化的方法:
数组初始化需要注意的细节:
输出字符串
常见错误
输出乱码时,查看字符数组中是否存储了结束符‘\0’
用子函数模拟输出字符串
scanf读取字符串
5.5 gets与puts讲解,strlen-strcmp-strcpy讲解
gets函数与puts函数
str系列字符串操作函数(初试没那么重要,对于机试更重要一些)
strlen的使用及原理
#include <stdio.h>
#include <string.h>
int mystrlen(char c[])
{
int i=0;
while(c[i])
{
i++;
}
return i;
}
int main() {
int len;
char e[20];
char c[20];
char d[100]="world";
gets(c);
puts(c);
len=strlen(c);//统计字符串的长度
printf("len=%d\n",len);
len=mystrlen(c);
printf("mylen=%d\n",len);
strcat(c,d);//把d中的字符串拼接到c中,c的位置不可以是字符串
puts(c);
strcpy(e,c);//把c中的字符串复制到e中,e的位置不可以是字符串
puts(e);
printf("c?d=%d\n", strcmp(c,e));
//stecmp比较的不是字符串的长度,而是比较字符的ASCII码值
//前大于后,返回值是正值,小于返回负值,相等返回0
return 0;
}
本节课OJ作业
作业一:输入N个数(N小于等于100),输出数字2的出现次数;
思路:输入一个整数和一个数组,并且统计2出现的次数
我的代码:
#include <stdio.h>
int main() {
int n,j=0;
int a[100];
scanf("%d",&n);
for (int i = 0; i < n; i++)
{
scanf("%d",&a[i]);
if(a[i]==2)
j++;
}
printf("%d",j);
return 0;
}
答案:
#include <stdio.h>
//读取一堆整型数,统计2出现的次数
int main() {
int element_count;//元素个数
int arr[100];
scanf("%d",&element_count);
for (int i = 0; i < element_count; i++)
{
scanf("%d",&arr[i]);//循环读取多个整型数
}
//判断整型数组中2出现的次数
int count=0;
for(int i = 0; i < element_count; i++)
{
if(arr[i]==2)
count++;
}
printf("%d",count);
return 0;
}
需要注意一些变量名称的设置
作业二:读取一个字符串,字符串可能含有空格,将字符串逆转,原来的字符串与逆转后字符串相同,输出0,原字符串小于逆转后字符串输出-1,大于逆转后字符串输出1。例如输入 hello,逆转后的字符串为 olleh,因为hello 小于 olleh,所以输出-1
我的代码:
#include <stdio.h>
#include <string.h>
//解决三个问题:一、逆转 二、比较 三、输出
int main() {
int len,i,result;
char c[30];
char d[30];
gets(c);
len=strlen(c);
for(i=0;i<len;i++) {
d[len - i - 1] = c[i];
}
result= strcmp(c,d);
if(result>0)
printf("%d\n",1);
else if (result<0)
printf("%d\n",-1);
else
printf("%d\n",0);
return 0;
}
答案:
#include <string.h>
//字符串翻转,翻转后比较与原字符串是否相等
//使用增量编写法
int main() {
int len,i,j,result;
char c[100];//原字符串
char d[100]={0};//翻转后的,初始化的目的就是让d有结束符
gets(c);
for(i=0,j= strlen(c)-1;i< strlen(c);i++,j--) {
d[j] = c[i];
}
result= strcmp(c,d);
if(result>0)
printf("%d\n",1);
else if (result<0)
printf("%d\n",-1);
else
printf("%d\n",0);
return 0;
}
注意:
一、windows下使用VS的集成开发环境不能使用gets
可以用
char c[100];
fgets(c,sizeof(c),stidin)
clion使用c99标准
二、注意数组d的要有结束符,解决方法就是初始化d
六、指针
6.2指针的本质(间接访问原理)
指针的定义
取地址操作符&与取值操作符*,指针本质
定义指针变量
指针变量所占用的空间大小
需要注意的4点
6.3指针的传递使用场景
为什么i的值不会改变?
函数调用不会改变变量的值,那怎么样才能改变变量的值呢?
6.4指针的偏移使用场景
指针的偏移
p存储着数组的起始地址0x61fe20,那为什么p+1是0x61fe24呢?
float *p也是偏移4个字节
指针与一维数组
6.5指针与malloc动态内存申请,栈与堆的差异
指针与动态内存申请(有很大概率会用到,非常重要)
空间不用时,要释放申请的空间
指针本身的大小和其指向空间的大小是两码事,指针本身的大小永远都是8字节。
栈空间与堆空间差异(了解)
堆的效率要比栈低得多,但是栈不能动态使用
OJ作业
作业一:输入一个整型数,存入变量i,通过子函数change把主函数的变量i除2,然后打印i,例如如果输入的为10,打印出5,如果输入的为7,打印出3
我的答案:
#include <stdio.h>
void change(int *j)
{
*j=*j/2;
}
int main() {
int i;
scanf("%d",&i);
change(&i);//传递变量i的地址
printf("%d\n",i);
return 0;
}
老师的答案:
#include <stdio.h>
//在子函数中改变main函数中某个变量的值
void change(int *j)//j=&i
{
*j=*j/2;
}
int main() {
int i;
int *p=&i;//如果定义一个指针变量,没有初始化,就是空的藏宝图
scanf("%d",p);
change(p);//传递变量i的地址
printf("%d\n",*p);
return 0;
}
作业二:输入一个整型数,然后申请对应大小空间内存,然后读取一个字符串(测试用例的字符串中含有空格),字符串的输入长度小于最初输入的整型数大小,最后输出输入的字符串即可(无需考虑输入的字符串过长,超过了内存大小);
我的答案:
#include <stdio.h>
void change(int *j)
{
*j=*j/2;
}
int main() {
int i;
scanf("%d",&i);
change(&i);//传递变量i的地址
printf("%d\n",i);
return 0;
}
老师的答案:
#include <stdio.h>
#include <stdlib.h>
//malloc的使用
int main() {
int n;//代表申请空间的大小
scanf("%d",&n);//读取
char c;
scanf("%c",&c);
//注意在scanf和gets中间使用scanf("%c",&c),清除标准输入缓冲区中的\n
char *p;
p=(char *)malloc(n); //申请n个字节大小的空间,强制类型转换为char*
fgets(p,n,stdin);//gets(p);容易产生访问越界所以被去掉了
puts(p);
return 0;
}
七、函数
八、结构体及C++引用讲解
8.2结构体-结构体对齐-结构体数组
结构体
#include <stdio.h>
struct student{
int num;
char name[20];
char sex;
int age;
float score;
char addr[30];
};//结构体类型声明,注意最后一定要加分号
int main() {
struct student s={1001,"lele",'M',20,85.4,"Shenzhen"};
//定义及初始化
//变量名不要和结构体类型名重名
struct student sarr[3];//定义一个结构体数组变量
int i;
//结构体输出必须单独去访问内部的每个成员
s.num=1003;
printf("%d %s %c %d %f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
printf("--------------------------------------\n");
// scanf("%d%s %c%d%f%s",&s.num,s.name,&s.sex,&s.age,&s.score,s.addr);
for(i=0;i<3;i++)
{
scanf("%d%s %c%d%f%s",&sarr[i].num,sarr[i].name,&sarr[i].sex,&sarr[i].age,&sarr[i].score,sarr[i].addr);
}
for(i=0;i<3;i++)//结构体数组的输出
{
printf("%d %s %c %d %f %s\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score,sarr[i].addr);
}
return 0;
}
结构体对齐
结构体大小必须是其最大成员(不包括数组)的整数倍
为什么要对齐呢?就是为了cpu高效的去取内存上的数据
#include <stdio.h>
struct student_type1{
double score;//double是一种浮点类型,8个字节,浮点分为float和double,记住有这两种即可
short age;//short 是整型,占2个字节
};
struct student_type2{
double score;
int height;//如果两个小存储之和是小于最大长度8,那么它们就结合在一起
short age;
};
struct student_type3{
int height;
char sex;
short age;
};
//结构体对齐
int main() {
struct student_type1 s1;
struct student_type2 s2={1,2,3};
struct student_type3 s3;
printf("s1 size=%d\n",sizeof(s1));
printf("s2 size=%d\n",sizeof(s2));
printf("s3 size=%d\n",sizeof(s3));
return 0;
}
8.3结构体指针与typedef的使用
结构体指针
一个结构体变量的指针就是该变量所占据的内存段的起始地址
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct student{
int num;
char name[20];
char sex;
};
//结构体指针的练习
int main() {
struct student s={1001,"wangle",'M'};
struct student sarr[3]={1001,"lilei",'M',1005,"zhangsan",'M',1007,"lili",'F'};
struct student *p;//定义了一个结构体指针变量
p=&s;
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1访问通过结构体指针去访问成员
printf("%d %s %c\n",p->num,p->name,p->sex);//方式2访问通过结构体指针去访问成员,用这种
p=sarr;
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1访问通过结构体指针去访问成员
printf("%d %s %c\n",p->num,p->name,p->sex);//方式2访问通过结构体指针去访问成员,用这种
printf("------------------------------\n");
p=p+1;
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式1访问通过结构体指针去访问成员
printf("%d %s %c\n",p->num,p->name,p->sex);//方式2访问通过结构体指针去访问成员,用这种
//下面给结构体指针p通过malloc申请空间,并对其成员赋值,再访问
//(struct student*)强制类型转换
p=(struct student*)malloc(sizeof(struct student));
p->num=100;
p->sex='M';
strcpy(p->name,"longge");
printf("------------------------------\n");
printf("%d %s %c\n",p->num,p->name,p->sex);
return 0;
}
typedef的使用
#include <stdio.h>
//stu 等价于 struct student,pstu等价于struct student*
typedef struct student{
int num;
char name[20];
char sex;
}stu,*pstu;
typedef int INGETER;//特定地方使用
//typedef的使用,typedef起别名
int main() {
//struct student s={0};
stu s={1001,"wangle",'M'};
stu *p=&s;//定义了一个结构体指针变量
pstu p1=&s;//定义了一个结构体指针变量
INGETER num=10;
printf("num=%d,p->num=%d\n",num,p->num);
return 0;
}
8.4c++引用的讲解
c++的引用讲解
#include <stdio.h>
//当你在子函数中要修改主函数中变量的值,就用引用,不需要修改,就不用
void modify_num(int &b)//形参中写&,要称为引用
{
b=b+1;
}
//C++的引用的讲解
//在子函数内修改主函数的普通变量的值
int main() {
int a=10;
modify_num(a);
printf("after modify_num a=%d\n",a);
return 0;
}
#include <stdio.h>
void modify_pointer(int *&p,int *q)//引用必须和变量名紧邻
{
p=q;
}
//子函数内修改主函数的一级指针变量
int main() {
int *p=NULL;
int i=10;
int *q=&i;
modify_pointer(p,q);
printf("after modify_pointer *p=%d\n",*p);
return 0;//进程已结束,退出代码为 -1073741819 ,不为0,那么代表进程异常结束
}
c++的布尔类型