文章目录
前言
本人于2024年3月份跟着王道C语言督学营学习,本文章仅作为课程代码记录,其间加入了一些本人的思考和见解,帮助新手小白更好地理解和掌握C语言,代码可供读者参考。
一、初级阶段(已更完)
1、课程导学-打印Hello world!
#include <stdio.h>
//打印Hello, World!
int main() {
printf("Hello, World!\n");
return 0;
}
2、 数据的类型、数据的输入输出
2.2 -1-printf
#include <stdio.h>
//练习printf
int main() {
int age=21;
printf("Hello %s, you are %d years old\n","Bob",age);
int i=10;
float f=96.3;
printf("student number=%-3d,score=%5.2f\n",i,f);//默认是右对齐,加一个负号变为左对齐
i=100;
f=98.21;
printf("student number=%3d,score=%5.2f\n",i,f);
return 0;
}
2.2 -2-cast强制类型转换
#include <stdio.h>
//强制类型转换
int main() {
int i=5;
float j=i/2;//这里做的是整型运算,因为左右操作数都是整型
float k=(float)i/2;
printf("%f\n",j);
printf("%f\n",k);
printf("Hello, World!\n");
return 0;
}
2.3 -1-decimal conversion十进制转换
#include <stdio.h>
//十进制转换
int main() {
int i = 123;
printf("%d\n",i);//十进制输出
printf("%o\n",i);//八进制输出是%o
printf("%x\n",i);//十六进制输出是%x
return 0;
}
2.4 -1-scanf
#include <stdio.h>
//scanf用来读取标准输入,scanf把标准输入输入内的内容,放到某个变量空间,因此变量必须取地址。
//scanf会卡住是因为,标准输入缓冲区是空的,才会卡住,不是空的就不会卡住。
int main() {
int i;
char c;
float f;
scanf("%d",&i);//浮点数写%f,字符型写%c,%d表示带符号整数。scanf读取的是键盘输入的字符串 ,写类型是为了将这些输入的字符串转换成对应的类型。
// &i表示取地址,一定要取地址。
printf("i=%d\n",i);//这里是把标准输入缓区的整型读走了,还有\n留在标准输入缓冲区
// fflush用于清空标准输入缓冲区的内容
fflush(stdin);//清空标准输入缓冲区里的内容,即\n
scanf("%c",&c);
printf("c=%c\n",c);//输出字符变量c
scanf("%f",&f);//scanf会忽略‘\n’,空格符等字符,因此上一步标准输入缓冲区里的‘\n’,被scanf忽略了,所以此时相当于标准输入缓冲区是空的,就会卡住。
printf("f=%f\n",f);//输出浮点数f
return 0;
}
2.4 -2-scanf-more
#include <stdio.h>
//scanf一次读多种数据类型
int main() {
int i,ret;
float f;
char c;
ret=scanf("%d %c%f",&i,&c,&f);//ret是scanf匹配成功的个数,可以辅助我们调试。在%c之前加入空格
printf("i=%d,c=%c,f=%5.2f\n",i,c,f);
return 0;
}
3、运算符与表达式
3.2 -1-arithmetic算术运算符
#include <stdio.h>
//练习算术运算符
int main() {
int result=4+5*2-6/3+11%4;
printf("result=%d\n",result);
return 0;
}
3.2 -2-relation关系运算符
#include <stdio.h>
//关系运算符,优先级小于算术运算符
//不能用数学上的连续判断大小来判断某个数
int main() {
int a;
while(scanf("%d",&a))
{
if(3<a && a<10)//a大于3同时a小于10,要这样写,而不能写成啊3<a<10
{
printf("a is between 3 and 10\n");
}
else
{
printf("a is not between 3 and 10\n");
}
}
return 0;
}
3.3 -1-logic逻辑运算符
#include <stdio.h>
//逻辑运算符练习
//记住优先级顺序,不要加多余的括号,会增加阅卷老师的阅卷难度,可能判错
int main()
{
int year,i,j=1;
while(scanf("%d",&year))
{
if(year%4==0 && year%100!=0 || year%400==0)//能被4整除并且不能被100整除 或 能被400整除 的年份称为闰年
{
printf("%d is leap year\n",year);
}else{
printf("%d is not leap year\n",year);
}
}
i=!!j; //非!的优先级大于赋值运算符=。 j=1,非一层是0,再非一层是1,所以i=1。 逻辑!是从右往左结合
printf("i value = %d\n",i);
return 0;
}
3.3 -2-short circuit短路运算
#include <stdio.h>
//逻辑与 和 逻辑或。 短路运算
int main() {
int i=0,j=1;
i && printf("This is i and &&: you can't see me\n");//当i为假时,不会执行逻辑与后的表达式,称为短路运算。当i为真时,则可以执行。
j && printf("This is j and &&: you can see me\n");
// 短路运算可以让我们的代码写的很短,很简洁。不需要写if,else语句
i || printf("This is i and ||: you can see me\n");
j || printf("This is j and ||: you can't see me\n");//当j为真时,不会执行逻辑或后的表达式,称为短路运算。当j为假时,则可以执行。
return 0;
}
3.3 -3-assign复合运算符
#include <stdio.h>
//赋值的练习,左值和右值的区别。复合运算符,如+=,*=的练习
int main() {
int a=1,b=2,c=3;
b=a+25;//b为变量时即可执行,不会报错。而写成a+25=b则会报错。
printf("b=%d\n",b);
a+=3;//表达的是a=a+3,简写时可以提高编译速度
printf("a=%d\n",a);
c*=3;//表达的是c=c*3,简写时可以提高编译速度
printf("c=%d\n",c);
return 0;
}
3.3 -4-sizeof求变量的字节
#include <stdio.h>
//sizeof运算符
int main() {
int i=0;
printf("i size is %d\n",sizeof(i));
return 0;
}
4 、选择和循环
4.2 -1-if-else语句
#include <stdio.h>
//练习if-else条件语句
int main() {
int i;
while(scanf("%d",&i))
{
// if(i>0)
// printf("i is bigger than zero\n");//该语句属于if内部,不建议用这种书写格式。大厂里一般要求加大括号{}
// printf("I know!\n");//该语句不属于if内部了。
if(i>0)//在if下加一个大括号{},养成良好的编程习惯
{
printf("i is bigger than zero\n");
}else{
printf("i is not bigger than 0\n");
}
}
return 0;
}
4.2 -2-if else Multi-branching多分支
#include <stdio.h>
//if-else 的多分支语句练习
int main() {
//以家庭用电费为例
int i;
float cost;
while(scanf("%d",&i))
{
if(i>500)//i>500
{
cost=0.15;
printf("cost=%.3f\n",cost);
}else if(i>300)//300<i<=500
{
cost=0.10;
printf("cost=%.3f\n",cost);
}else if(i>100)//100<i<=300
{
cost=0.075;
printf("cost=%.3f\n",cost);
}else if(i>50)//50<i<=100
{
cost=0.05;
printf("cost=%.3f\n",cost);
}else//i<=50
{
cost=0;
printf("cost=%.3f\n",cost);
}
}
return 0;
}
4.3 -1-while循环
#include <stdio.h>
//while循环计算从1-100的和
int main() {
int i=1,total=0;//变量初始化
while(i<=100)//注意while后面不加分号。加分号会导致while进入死循环。
{
total+=i;//把i加到total上
i++;//i++等价于i=i+1。 循环内必须要有使循环趋近于假的表达式。
// 在循环体内若没有让while趋近于假的操作,则会进入死循环。
}
printf("1 add to 100: \ntotal = %d\n",total);
return 0;
}
4.3 -2-for循环
#include <stdio.h>
//for循环实现从1加到100
int main() {
int i,total;
for (i=1,total=0 ; i<=100 ; i++) //在for加分号不会死循环,但是会结果不对。
//第一个表达式初始化,第二个表达式判断条件,第三个表达式使循环趋近于假
// 且存在第三个表达式i++,不会忘记写使得循环趋近于假的表达式,因此比while循环更好。
{
total+=i;
}
printf("total=%d\n",total);
return 0;
}
4.3 -3-continue语句
for循环的continue(推荐使用for循环)
#include <stdio.h>
//continue语句for循环计算1-100的奇数和
int main() {
int i, total;
for(i=1,total=0;i<=100;i++)
{
if(i%2==0)
{
continue;//跳过continue下面的所有语句,即跳过本次循环,直接进入到下一次循环。过滤掉偶数
}
total+=i;
}
printf("total=%d\n",total);
return 0;
}
while循环的continue
#include <stdio.h>
//continue语句while循环计算从1-100的奇数和
int main() {
int i=1,total=0;
while(i<=100)//注意while后面不加分号。加分号会导致while进入死循环。
{
if(i%2==0)
{
i++;//这里循环内部也需要加i++,要不会使while循环进入死循环。
continue;//continue是跳过后面所有的语句,因此若上面不加i++这条语句,则就不会执行使循环趋于假的语句,就会进入死循环。
}
total+=i;//把i加到total上
i++;
}
printf("total = %d\n",total);
return 0;
}
4.3 -4-break语句
#include <stdio.h>
//break:直接结束整个循环过程
//例子;从1开始累加,当累加的和total>2000时,结束for循环
int main() {
int i,total;
for (i=1,total=0 ; i<=100 ; i++)
{
if(total>2000)
{
break;//直接跳出循环,不执行下面的语句total+=i,也不执行i++。直接执行循环外的语句printf()
}
total+=i;
}
printf("i=%d,total=%d\n",i,total);
//结果为i=64,total=2016//这里面的i=64,实际上没有加到total里,total是1-63的和。
// 因为在i=64时进入循环,判断出total已经>2000,此时就跳出循环,而没有执行total+=64
return 0;
}
4.3 -5-two-level loop循环嵌套(两层for循环)
例子1:输出每次外循环的次数,内循环的0-4
#include <stdio.h>
//两层for循环
int main() {
int i,j;
for (i=0 ; i<5 ; i++)
{
printf("i=%d\n",i);//打印i,就知道外层循环开始了,也知道是第几次外层循环。
for (j=0 ; j<5 ; j++)//外层循环每执行一次,内层循环执行五次。
{
printf("%d",j);//打印五次j,没有加\n,就会输出在一行内。
}
printf("\n");//内层循环五次后,在外层循环中,输出一个换行。也就是0 1 2 3 4后面换行
}
return 0;
}
4.3 -6-two-level loop-printf star循环嵌套(两层for循环)
例子2:输出一个✳三角形,即第一行输出1个✳,第二行输出2个✳,第三行3个,第四行4个,第五行5个
#include <stdio.h>
//输出一个✳三角形,即第一行输出1个✳,第二行输出2个✳,第三行3个,第四行4个,第五行5个
int main() {
int i,j;
for(i=1;i<6;i++)//控制要输出多少行
{
for(j=0;j<i;j++)//内层循环控制一行中每一列显示什么
{
printf("*");
}
printf("\n");//一行结束输出一个换行
}
return 0;
}
5 、一维数组与字符数组
5.2 -1-array数组
#include <stdio.h>
//一维数组
int main() {
//(1)定义数组时对元素赋初值
int a[10]={1,2,3,4,5,6,7,8,9,10};//定义一个整型数组,数组名为a,它有10个元素。
//数组名与变量名命名规则相同:由数字,字母,下划线组成,且只能由字母和下划线开头。
//方括号内只能为常量表达式,即为常量或者符号常量。但是不能为变量(最新的C标准支持,但是考研初试的时候一般不支持,所以不建议这么写。例如:int k=3,a[k];)。
//数组实际访问的时候是a[0]到a[9]
//不能写成如下这种形式,是错误写法,会编译不通的。
//int a[10];
//a[10]={1,2,3,4,5,6,7,8,9,10};
//(2)可以只给一部分元素赋值
int b[10]={1,2,3,4,5};//这表示只给前五个元素赋初值,后五个元素的值为0
//(3)如果要使一个数组中全部元素的值为0
int c[10]={0};//语言规定会把后面9个都初始化成0
//(4)在对全部数组元素赋初值时,由于数据个数已经确定,因此可以不指定数组的长度
int d[]={1,2,3,4,5};//但是不建议这么写,因为需要导师自己数个数,很大程度上给阅卷造成困难。
//(5)查看数组的大小,用sizeof()运算符
printf("%d\n",sizeof(d));
return 0;
}
5.3 -1-array-access out of bounds数组的访问越界
1.访问越界非常危险,在后来出现的语言中,会对访问越界有检查,因为每次赋值时都做检查的话,对语言的性能会有所降低,因此我们的C语言是没有检查的,所以这个访问越界的问题一直存在,只能在写代码时规避这个问题。初试不考,而复试可能会考。
2.访问越界最有可能发生在写for循环时,在for循环中访问每个数组的元素,但是这个for循环的边界没有控制好,就会造成访问越界。
3.数组另一个值得注意的地方是:编译器并不检查程序对数组下标的引用是否在数组的合法范围内。
#include <stdio.h>
//数组的访问越界
int main() {
int a[5]={1,2,3,4,5};//定义数组时,数组长度必须固定。
int j=20;
int i=10;
a[5]=6;//访问越界
a[6]=7;//越界访问会造成数据异常
printf("i=%d\n",i);//i我们并没有赋值,但是值却发生改变
return 0;
//访问越界最有可能发生在写for循环时,在for循环中访问每个数组的元素,但是这个for循环的边界没有控制好,就会造成访问越界。
//数组另一个值得注意的地方是:编译器并不检查程序对数组下标的引用是否在数组的合法范围内。
}
5.3 -2-array-transmit数组的传递
一维数组的传递,数组长度无法传递给子函数,下面这个例子,只能输出,1和2
#include <stdio.h>
//子函数是把某一个常用功能封装起来的作用
//void是函数不需要返回值,就是最后可以不需要写return
//数组名传递到子函数后,子函数的形参接收到的是数组的起始地址,因此不能把数组的长度传递给子函数。
void print(int a[])//一维数组的传递,数组长度无法传递给子函数,就是a[]内可以不写数字
{
int i;
for(i=0;i<sizeof(a)/sizeof(int);i++)//在子函数内,不能用sizeof(a)得到数组大小的,因为数组名传递到子函数的时候,弱化成了指针,指针是8个字节的。
{
printf("%d\n",a[i]);
}
}
//main函数就是主函数
int main() {
int a[6]={1,2,3,4,5,6};
print(a);//调用print函数
return 0;
}
以下正确的写法,可以在子函数内部再定义一个额外的变量length来存储数组的长度。在外部函数得到数组的长度,赋值给子函数的形参length。
//以下是正确的写法:
#include <stdio.h>
void print(int a[],int length)
{
int i;
for(i=0;i<length;i++)
{
printf("%d",a[i]);
}
a[3]=20;//在子函数内可以修改数组的值
printf("\n");
}
//main函数就是主函数
int main() {
int a[5]={1,2,3,4,5};
print(a,sizeof(a)/sizeof(int));//数组在传递给子函数时,他的长度是传递不过去的,但是我们可以定义一个新的变量length
printf("a[3]=%d\n",a[3]);//查看结果是否被修改为20
return 0;
}
5.4 -1-char array字符数组
字符数组初始化表达的两种方法
#include <stdio.h>
//字符数组初始化
int main() {
char c[10]={'I','a','m','h','a','p','p','y'};//一般不用这种初始化,太麻烦
char m[10]="Iamhappy";//使用这种方式初始化字符数组。
}
字符串如何输出:
%s的输出规则,其实是内部有一个循环的,需要有’\0’来结束循环。
因此如果用此赋值方法 char b[5]={‘h’,‘e’,‘l’,‘l’,‘o’};是没有结束符’\0’的,会导致出现乱码。
#include <stdio.h>
int main() {
//因为C语言规定字符串的结束标志为'\0',所以写char c[5]="hello";时就会访问越界
char a[6]="hello";
printf("%s\n",a);//使用%s来输出一个字符串,直接把字符数组名放到printf后面位置。
// %s内部是有循环的,才能将字符串一个一个输出,而字符串里的'\0'就是循环结束的标志,只是我们感知不出这个循环。
//输出字符串乱码时,要去查看字符数组中是否存储了结束符'\0'
char b[5]={'h','e','l','l','o'};//强行给每个元素赋值,就没有结束符'\0',会导致%s输出结果时出现乱码。
printf("%s\n",b);
return 0;
}
模拟printf(“%s”,c)的操作
#include <stdio.h>
//模拟printf("%s",c)的操作
void print(char d[])
{
int i=0;
while(d[i])//当走到结束符'\0'时,循环结束
{
printf("%c",d[i]);
i++;
}
printf("\n");
}
int main() {
//模拟printf("%s",c)的操作
char d[5]="how";
print(d);
return 0;
}
5.4 -2-scanf-string scanf读取字符串
%s会忽略空格和换行符,当它到达空格时,匹配就结束了。
#include <stdio.h>
//scanf读取字符串。
//初试不会用到scanf,但是机试的时候需要用到。
int main() {
char c[10];
scanf("%s",c);//整型、浮点型、字符型变量都要取地址,是因为scanf必须要放一个地址。
//而这里不需要取地址是因为,字符数组名c中,本来就存储了数组的起始地址,记住就好。当然取地址&c也没错。
// scanf函数输入会自动帮你放结束符,就是输入的是hello,但其实传进去的是hello\0
// 在scanf里,%s会忽略空格和换行符,当它到达空格时,匹配就结束了。因此输入how are 输出是how
printf("%s\n",c);
return 0;
}
要解决这个问题,就分别用两个数组来存储
#include <stdio.h>
int main() {
char d[10];
char e[10];
scanf("%s%s",d,e);
printf("d=%s,e=%s\n",d,e);
return 0;
}
5.5 -1-gets-puts
#include <stdio.h>
//gets一次读取一行
int main() {
char c[20];
gets(c);//gets()中放入我们字符数组的数组名即可。
//gets遇到\n,就不会存储\n,而是将其翻译为空字符\0
printf("%s\n",c);
//puts()只能用于输出字符串,同时多打印一个换行符
puts(c);//puts等价于printf("%s\n",c);并且puts()里面所放的参数只能是字符数组名
return 0;
}
5.5 -2-str系列字符串操作函数
#include <stdio.h>//头文件含义:std是standard的意思,i是input,o是output。
#include <string.h>//字符串处理头文件
//模拟strlen的操作
int mystrlen(char c[])
{
int i=0;
while(c[i])//找到结束符后,循环结束,从而得出字符串长度。
{
i++;
}
return i;
// 例如:输入how,i[0]进入循环,i++,i=1;i[1]进入循环,i++,i=2;i[2]进入循环,i++,i=3;
// i[3]='\0',此时就不进入循环了,返回i的值即为3,恰为字符串的长度。
}
int main() {
int length;
char c[20];
char d[100]="world";
char e[100];
gets(c);
puts(c);
//strlen()统计字符串的长度,并且\0作为结束符,是不会统计进去的。
length=strlen(c);//使用strlen()必须先引用字符串处理头文件#include <string.h>
printf("strlen: length=%d\n",length);
//模拟strlen的操作
length=mystrlen(c);
printf("mystrlen: length=%d\n",length);
//strcat练习——字符串拼接
strcat(c,d);//把d中的字符串拼接到c中。且前一个实参只能是字符串数组,后一个实参可以是“how”这种直接输入的字符串。
puts(c);
//strcpy练习——字符串复制
strcpy(e,c);//把c中的字符串原模原样的复制到e中。且前一个实参只能是字符串数组,后一个实参可以是“how”这种直接输入的字符串。
puts(e);
//strcmp练习——字符串比较大小
printf("c:hello ? e =%d\n", strcmp(c,e));//c和e相等,返回值是0
printf("c:hello ? how =%d\n", strcmp(c,"how"));//c="hello"与“how”相比较,实际是从左到右比较每一个字母的ASCII码值。
//总结:
// c>"how",返回值是正值
// c=e,返回值是0
// c<"how",返回值是负值
return 0;
}
运行结果如下:
6、指针
6.2 -1-pointer
指针的定义,取地址操作符&与取值操作符*,指针本质
说某个变量的地址的时候,都是说它的起始地址。一个变量的地址称为该变量的指针。直接访问就类似直接拿到金元宝;间接访问就是通过指针这个藏宝图,去拿到金元宝。指针的大小:64位的程序就是8字节;32位的程序就是4字节。可以通过单步调试,输入sizeof(i_pointer)来查看大小。
#include <stdio.h>
//指针的定义
int main() {
int i=5;
int *i_pointer;//定义了一个指针变量,指针变量前面的*,表示该变量为指针型变量。i_pointer就是指针变量名,而不是*i_ipointer。
//定义指针变量时,要将*与变量挨着。因此要声明三个指针变量,就要这么写:int *a,*b,*c;
printf("%d\n",i);//直接访问
//取地址操作符&与取值操作符*,指针本质
int a=6;
int *b;
b=&a;//指针变量的初始化,一定是某个变量取地址来赋值,不能随机写个数。
//变量的类型和指针的类型一定要相同。
//&和*的运算符优先级相同,但要按自右向左方向结合。
printf("*b=%d\n",*b);//间接访问
return 0;
}
6.3 -1-change不使用指针,在子函数改值
#include <stdio.h>
//不使用指针变量,看在子函数内去改变某个变量的值是否能实现?
void change(int j)
{
j=5;
}
int main() {
int i=10;
printf("befoer change i=%d\n",i);
change(i);//C语言的函数调用是值传递,实参i赋值给形参j,但是i和j分别各有一个地址,所以改变了j=5是j地址内的值变成5,而i地址内的值不会改变。
printf("after change i=%d",i);
//操作系统必考概念:程序启动起来,就是进程
return 0;
}
6.3 -2-change-pointer指针的传递(在子函数改值)
#include <stdio.h>
//使用指针变量,看在子函数内去改变某个变量的值是否能实现?
void change(int *j)//j是形参
{
*j=5;//间接访问得到变量i的空间,就可以把i的值变为5。
// *是取值运算符,取的是j的值,而j就是i的地址,而i的地址对应的值就是i的值,这就是间接访问。
//*j等价于变量i,只是间接访问。
}
int main() {
int i=10;
printf("befoer change i=%d\n",i);
change(&i);//此时并没有违背C语言的值传递规则,这时是把变量i的地址值传给j
printf("after change i=%d",i);
return 0;
}
6.4 -1-pointer-calculate指针的偏移(整型数组)
下面以整型数组为例子,帮助大家更好地理解
#include <stdio.h>
//指针的偏移使用场景,也就是对指针进行加和减。
//下面以整型数组为例
#define N 5
int main() {
int a[N]={1,2,3,4,5};//定义一个数组.
// 数组名内存储了数组的起始地址,a中存储的就是一个地址值。
int *p;//定义了一个指针变量p
p=a;//因为数组存储的就是一个地址值,所以无需取地址,并且该地址是a的起始地址。
int i;
//不使用指针来遍历循环
printf("no use pointer:");
for(i=0;i<N;i++)
{
printf("%3d",a[i]);
}
printf("\n--------------------------------------\n");
//使用指针来遍历循环
printf("use pointer:");
for(i=0;i<N;i++)
{
printf("%3d",*(p+i));//p是a的起始地址,那么*p对应的就是a[0],因为*p一次取的长度就是基类型的长度。
//p+1,是指针的偏移,偏移的长度也是其基类型的长度,也就是sizeof(int),这样通过*(p+1)就可以获得元素a[1]。
//因此写*(p+i)与a[i]是等价的。
}
printf("\n---------------------------------------\n");
//使用指针,逆序输出
p=&a[4];//让p指向a的最后一个元素的起始地址
printf("use pointer and reverse:");
for(i=0;i<N;i++)
{
printf("%3d",*(p-i));
}
printf("\n");
//float *p; p的加减也是偏移4个字节
return 0;
}
6.4 -2-pointer-array指针的偏移(字符数组)
下面以字符数组为例,指针与一维数组的传递和偏移练习
#include <stdio.h>
//指针与一维数组的传递和偏移练习
//C语言的子函数形参位置,并没有数组变量的设计,我们把数组名实参传递给子函数时,形参接收到的是数组名内存储的数组的起始地址。
//数组名作为实参传递给子函数时,是弱化为指针的,因此sizeof时,就是指针的大小,即8个字节。
//下面以字符数组为例
void change(char *d)
{
*d='H';//改值方式第一种,指针改值
d[1]='E';//改值方式第二种,下标法改值
*(d+2)='L';//指针偏移改值
}
int main() {
char c[10]="hello";
puts(c);
change(c);
puts(c);
return 0;
}
6.5 -1-pointer-malloc指针与动态内寸申请(非常重要)
数组需要固定长度很不方便,其实C语言的数组长度固定是因为其定义的整型、浮点型、字符型变量、数组变量都在栈空间中。如果需要使用的空间大小不确定,那么就要使用堆空间。
本节内容非常重要,考研初试,机试,复试都会考到!
#include <stdio.h>
#include <stdlib.h>//malloc要使用的头文件
#include <string.h>//strcpy要使用的头文件
//本节内容非常重要,考研初试,机试,复试都会考到
int main() {
int size;
char *p;
scanf("%d",&size);//输入要申请的空间大小
//malloc返回的void*代表无类型指针
//void*类型的指针是不能偏移的,因此不会定义无类型指针。
p=(char*) malloc(size);//前面的(char*)是强制类型转换
//可以用类似数组的方法存储字符数据
p[0]='H';
p[1]='O';
p[2]='W';
p[3]='\0';
puts(p);//gets和puts接口里所放的是指针类型。
//也可以用strcpy()函数存储字符数据
strcpy(p,"malloc success");
puts(p);
//使用free(),释放申请的空间
free(p);//我们申请的空间,使用完之后必须要释放申请的空间,否则初试时会扣分。
//free时必须使用malloc申请时返回的指针值,不能进行指针偏移,也就是不能写p+1。
//若要进行指针偏移,可以另外再定义一个指针变量,到时候free该变量就行。
printf("free success\n");
return 0;
}
6.5 -2-heap-stack栈空间与堆空间的差异
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//堆和栈的差异
//函数栈空间释放后,函数内的所有局部变量消失
//出现这种情况,就说明是对原理理解不够透彻
char* print_stack()
{
char c[100]="I am print_stack function";
char *p;
p=c;
puts(p);
return p;
}
//堆空间在整个进程中一直有效,不会因为函数执行结束而释放(可以这么写,不出现乱码)
char *print_malloc()
{
char *p=(char*) malloc(100);
strcpy(p,"I am print_malloc function");
puts(p);
return p;
}
int main() {
char *p;
p=print_stack();
puts(p);//因为在上一条代码运行完之后,print_stack()的栈空间就被释放掉了,供下一个函数使用。
//而在子函数内把栈空间内数据的起始地址给返回回来,然后这里外部主函数接着去打印,就会出现乱码。因为栈空间被释放了,后面的内容被别人踩了。
printf("----------------------------\n");
p=print_malloc();
puts(p);
free(p);//只有通过free时,堆空间才会释放。
return 0;
}
栈空间
堆空间
7、函数
7.2 -1-function函数的声明与定义
(1)不创建子文件的代码:
#include "func.h"//双引号是在该目录下去找头文件,相当于是找我们自定义的头文件时要用双引号。
//函数的声明与定义
//这里为函数的声明
void print_message();//先声明。函数的声明。
int main() {
print_message();//后调用。函数的调用。当函数没有声明时,默认返回的类型是int类型,与定义的void类型冲突,运行时会有警告。
printf("Hello, World!\n");
return 0;
}
//这里为函数的定义
void print_message()
{
printf("how do you do\n");
}
(2)创建子文件的代码:
创建.c源文件/.h头文件:右键项目名称,新建,选择C/C++源文件/C/C++头文件,勾上添加到目标,点确定。
①func.h
//
// Created by lenovo on 2024/3/30.
//
#ifndef INC_1_FUNCTION_FUNC_H
#define INC_1_FUNCTION_FUNC_H \
//共同使用的头文件,可以写在这里。
#include <stdio.h>//<>是在标准库下去找头文件
//函数声明也放在这里,有可能会经常使用到,避免重复编写代码。
void print_message();//print_message函数的声明
int print_star(int i);//print_star函数声明,有形参,有返回值。
#endif //INC_1_FUNCTION_FUNC_H
②func.c
函数可以嵌套调用,但是不能嵌套定义。如下例:print_message函数嵌套调用print_star。
各个函数之间可以互相调用,但是不能调用main函数。
//
// Created by lenovo on 2024/3/30.
//
#include "func.h"
//这里为函数的定义,也叫函数的实现
//print_star函数
int print_star(int i)
{
printf("***************************\n");
printf("print_star%d\n",i);
return i+3;
}
//print_message函数
//可以调用print_star,就是嵌套调用
void print_message()
{
printf("how do you do\n");
print_star(3);//3是实参
}
③main.c
按住ctrl+鼠标左键,可以跳转到对应的函数,查看源码。
#include "func.h"//双引号是在该目录下去找头文件,相当于是找我们自定义的头文件时要用双引号。
//函数的声明与定义
//这里为函数的声明
int main() {
//print_star调用,返回值的应用
int a=10;
a=print_star(a);//print_star函数有返回值,return i+3。
printf("a=%d\n",a);//所以此时a变为13。
printf("-------------------------------------------\n");
//print_message嵌套调用print_star
print_message();//按住ctrl+鼠标左键,可以跳转到对应的函数,查看源码。
printf("-------------------------------------------\n");
//print_star调用,返回值也可以不使用
print_star(5);
return 0;
}
7.3 -1-recursion递归函数
递归的核心是①找公式②编写结束条件
例子一:求n的阶乘
#include <stdio.h>
//例子一:求n的阶乘
int f(int n)
{
//一定要有结束条件,结束条件一定是在return之前
if(1==n)//第②步写结束条件
{
return 1;
}
return n*f(n-1);//第①步找递归关系式
}
int main() {
int n;
scanf("%d",&n);
printf("f(%d)=%d\n",n,f(n));
return 0;
}
例子二:上台阶
假如有n个台阶,一次只能上1个台阶或2个台阶,请问走到第n个台阶有几种走法? 为便于读者理解题意,这里举例说明如下:假如有3个台阶,那么总计就有3种走法:第一种为每次上1个台阶,上3次;第二种为先上2个台阶,再上1个台阶;第三种为先上1个台阶,再上2个台阶。
#include <stdio.h>
//例子二:上台阶
int step(int n)
{
if(1==n||2==n)//第②步写结束条件,结束条件一定是在return之前
{
return n;//当台阶是1个或者两个时,递归结束
}
return step(n-1)+step(n-2);//第①步找递归关系式
}
int main() {
int n;
scanf("%d",&n);
printf("f(%d)=%d\n",n,f(n));
printf("step(%d)=%d",n, step(n));
return 0;
}
7.4 -1-global-variable全局变量和局部变量
#include <stdio.h>
int i=10;//i是一个全局变量,不建议使用,初试非必要不使用。全局变量在数据段,不会被释放掉。
//全局变量是从定义的位置到文件的末尾,这个区段内有效,并不是全局有效,即定义位置的上面是无效的。
void print(int a)//形参可以看成一个局部变量
{
printf("I am print i=%d\n",i);//这里获取的是数据段的i
}
int main() {
{
int j=5;//局部变量只在离自己最近的大括号内有效
}
//printf("j=%d",j);此语句运行时会报错,因为j只在离自己最近的大括号内有效
//for循环括号内定义的变量,也是局部变量,在循环体外也不可用
print(5);
printf("main i=%d\n",i);
int i=5;//这是局部变量在栈空间
print(5);
return 0;
}
8、 结构体及C++引用讲解
8.2 -1-struct结构体的定义、初始化、结构体数组
#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"};//结构体变量定义及初始化
//结构体输出必须单独去访问内部的每个成员
s.num=1003;
printf("%d %s %c %d %5.2f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
scanf("%d%s %c%d%f%s",&s.num,s.name,&s.sex,&s.age,&s.score,s.addr);//%c前必须要空格,因为它不会忽略空格和换行符。
// s.name和s.addr不需要取地址,因为s.name和s.addr是字符数组,本身存储的就是变量的起始地址。
printf("%d %s %c %d %5.2f %s\n",s.num,s.name,s.sex,s.age,s.score,s.addr);
printf("---------------------------------------------------------------------------------------------------------\n");
struct student sarr[3];//定义一个结构体数组变量
int i;
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 %5.2f %s\n",sarr[i].num,sarr[i].name,sarr[i].sex,sarr[i].age,sarr[i].score,sarr[i].addr);
}
return 0;
}
8.2 -2-struct-size结构体对齐
结构体对齐也叫计算结构体的大小。
为什么要对齐?答:就是为了cpu高效地去取内存上的数据。
结构体的大小必须是其最大成员的整数倍。
如果两个小存储之和是小于最大长度的,那么它们就结合到一起。
#include <stdio.h>
//结构体对齐,实际就是计算结构体的大小
struct student_type1{
double score;//double是一种浮点类型,8个字节,浮点类型分为float和double,记住这两种即可。
short age;//short是整型,占两个字节。2要按8个对齐。
// 所以s1的大小是16。
};
struct student_type2{
double score;//这是8个字节
int height;//这是4个字节
short age;//这是2个字节
//如果两个小存储之和是小于最大长度的,那么它们就结合到一起。
//int和short的字节总数是6,是小于8的,可以填到一起,因此这两个总的是8。
//若把int改成char,char和short挨在一起就可以一起占用4个字节,这样写可以减小结构体占用的内存大小,而这里的大小改变就是由4变为8。
// 所以s2的大小是16。
};
struct student_type3{
int height;//这里是4个字节
char sex;//这里是1个字节
short age;//这是2个字节
//char和short的字节总数是3,是小于4个字节的,可以填到一起,因此这两个是占4个字节的。
// 所以s3的大小是8。
};
int main() {
struct student_type1 s1;
struct student_type2 s2;
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 -1-struct-pointer结构体指针
#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',1002,"zhangsan",'M',1003,"lili",'F'};
struct student *p;//定义了一个结构体指针变量
// 通过结构体指针访问一个结构体变量
p=&s;
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式一通过结构体指针去访问成员。加括号是因为取值的优先级没有点的优先级高。
printf("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
printf("--------------------------------------\n");
// 通过结构体指针访问一个结构体数组变量
p=sarr;//sarr与&sarr[0]等价
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式一通过结构体指针去访问成员。加括号是因为取值的优先级没有点的优先级高。
printf("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
printf("--------------------------------------\n");
// 结构体指针的偏移,偏移的大小是一个基类型的大小,也就是一个结构体的大小
p=p+1;
printf("%d %s %c\n",(*p).num,(*p).name,(*p).sex);//方式一通过结构体指针去访问成员。加括号是因为取值的优先级没有点的优先级高。
printf("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
printf("--------------------------------------\n");
// 下面给结构体指针p通过malloc申请空间,并对其成员赋值,再访问
p=(struct student*)malloc(sizeof(struct student));//强制类型转换,因为malloc的返回类型是void。
p->num=100;
p->sex='M';
strcpy(p->name,"longge");
printf("%d %s %c\n",p->num,p->name,p->sex);//方式二通过结构体指针去访问成员,使用这种方式。
return 0;
}
8.3 -2-typedef声明新的类型名来代替原有的类型名
#include <stdio.h>
//typedef的使用,typedef就是起别名
typedef struct student{
int num;
char name[220];
char sex;
}stu,*pstu;//这里就是起别名。stu等价于struct student;pstu等价于struct student*
typedef int INTEGER;//给int起别名。
int main() {
stu s={0};
stu *p=&s;//定义了一个结构体指针变量
pstu p1=&s;//定义了一个结构体指针变量
INTEGER num=10;
printf("num=%d,p->num=%d\n",num,p->num);
return 0;
}
8.4 -1-quoteC++的引用讲解
注意这里要创建的文件是C++文件。
在子函数内修改主函数的普通变量的值
#include <stdio.h>
//C++的引用
//在子函数内修改主函数的普通变量的值
//使用场景当你在子函数中,要修改主函数中变量的值,就用引用&;不要修改,就去掉引用&。
void modify_num(int &b)//形参中写&这个符号,要称为引用,不是取地址
{
b=b+1;
}
int main() {
int a=10;
modify_num(a);
printf("after modify_num a=%d\n",a);
return 0;
}
8.4 -2-modify-pointerC++的指针引用
C++文件
在子函数内修改主函数的一级指针变量的值
#include <stdio.h>
//在子函数内修改主函数的一级指针变量的值
void modify_pointer(int *&p,int *q)//引用&必须和变量名紧邻,否则会编译不通
{
p=q;
}
int main() {
int *p=NULL;//NULL就是0,当是0时,就是什么地方都不指向
int i=10;
int *q=&i;
modify_pointer(p,q);
printf("after modify_pointer *p=%d\n",*p);
return 0;//进程已结束,退出代码为 -1073741819 (0xC0000005) //执行退出代码不为0,那么代表进程异常结束了。
}
8.4 -3-bool C++的布尔类型
#include <stdio.h>
//C++的布尔类型
int main() {
bool a=true;
bool b=false;
printf("a is true=%d,b is false=%d\n",a,b);//C++规定true的值是1,false的值是0
return 0;
}
总结:以上就是C语言初级阶段的内容,本文仅仅简单介绍了C语言和C++的使用。欢迎读者批评指正,多多交流,有意者可以一起探讨有关编程的相关问题。私信可领取源代码压缩包。