我们紧接着上一章了解C语言-上往下分享C语言的基础知识,在这之前我们先引入一个表格,叫ASCLL码表,它向我们展示了每个Ascll码值对应的字符,这里我们可以跳转到Ascll码表了解一下.我们可以看见Ascll码值从65到90是大写字母A到Z,Ascll码值97到122是小写字母a到z.大小写字母相差32个Ascll值,也就是相差一个空格字符(space).我们先了解一下,后期做题可能会用
我们说函数就像一个工厂一样,你给我一些原料(实际参数),我们在工厂(函数)中加工后(代码实现)给你返回一个成品(return一个值).像我们之前使用的scanf(屏幕输入)和printf(屏幕输出)就是两个库函数, 库函数是C语言的编译器提供的现成的函数,直接可以使用,但是需要引用相应的头文件,如
就是scanf和printf的头文件.我们说C语言有自带的函数也有人为创建的函数,比如我们现在写一段代码用来求a和b的和.
#include
int main()
{
int a=10, b=20;
int c = a + b;
printf("%d", c);
return 0;
}
这样求a和b的和是在main函数中实现的,那我们想不在main函数中实现我们想自己写一个函数实现,自己就创一个函数.
我们说当我们写好一个函数之后,它的x和y是形式参数,是没有实际的值的,所以我们需要在main函数中调用我们写的函数才能让函数运行起来
其实我们可以发现我们自己写的函数中是可以优化的,我们可以不用定义变量p直接返回x+y也能实现一样的功能所以我们来把所有代码写出来:
#include
int add(int x, int y)
{
return x+y;
}
int main()
{
int a=10, b=20;
int c = add(a, b);
printf("%d", c);
return 0;
}
我们说函数有形式参数和实际参数,当我们调用函数时我们实际上是将实际参数的值临时拷贝一份给形式参数,形式参数和实际参数除了值相同外是没有任何联系的,它们拥有不同的存储空间,所以说我们改变形参的值对实参是没有影响的,这个稍做了解后面会详细讲解.
我们用函数实现了两个数相加后可能会有疑问,我们新创建一个函数写的代码比直接在main函数中实现两个数相加写的还多,这不是多此一举吗?其实不是这样的,第一点我们在main函数外写函数会使我们的代码结构更加分明,更加容易被别人读懂.其次,当我们想要去做一个项目的时候,比如我们的加减法或者排序需要在我们项目中多次使用时,如果我们不自定义函数,我们将在main函数中遇见一次循环就自己动手实现一次,这明显比我们在main函数外实现一次后直接在main函数内调用麻烦得多.所以我们说函数可以简化代码,代码复用.
当我们想要存储10个数据,100个数组时,如果我们一一定义10个变量(100个变量),这样十分的冗杂.这里C语言给我们提供了数组这样一个定义:数组即为一组相同类型元素的集合.,我们一个数组中的所有元素都具有同一种类型(如整型,浮点型,字符).下面我们来定义一组数组.
我们的C语言规定:数组的每个元素都有一个下标,下标是从0开始的。
并且数组是可以通过下标来访问的。比如:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};//整型数组
// 0 1 2 3 4 5 6 7 8 9 整型1的下标是0,整型2的下标为1,以此类推
我们可以利用这个性质把数组中的元素打印出来
#include
int i = 0;
while (i < 10)
{
printf("%d ", arr[i]);//i从0递增到9,我们就可以打印数组下标从0到9的元素.
i = i + 1;//也可以写成i++,后面会介绍
}
printf("%d\n", arr[5]);//也可以直接打印数组下标为5的元素(第六个元素)
return 0;
这里的加减乘除和数学中的可能有所不同.加减的效果和数学中的加减是相同的.
我们来看看乘法,它的符号用的是*而不是x.
我们熟悉的除法用/表示,它的使用和数学中的使用不一样!首先,C语言中的除法分为整数除法和浮点数除法,我们先来了解一下整数除法,如下:
#include
int main()
{
int a = 5;
int b = 2;
int c = a / b;//整数除法中除号两边的变量必须都为整型.
printf("%d", c);//结果为2,而不是2.5,是因为C语言除法的结果是整除之后得到的商!
return 0;
}
浮点数除法也就是我们的小数除法,我们还是用代码的形式给大家讲解:
int main()
{
int a = 7/2;
printf("%d\n", a);//这里的结果为3,因为它是整数除法
printf("%lf\n", 7.0 / 2);//结果为3.500000
printf("%lf\n", 7 / 2.0);//结果为3.500000
printf("%lf\n", 7.0 / 2.0);//结果也为3.500000
return 0;
我们可以发现只要除号左右两边至少有一个为浮点型,它就是浮点数除法,%lf代表我们要打印float类型的数据(精读为小数点后6位)
最后这个地方我们来看看取余操作符%,和它的名字一样,%是用来取余的,== 值得注意的是取余操作符左右两边的操作数必须都为整型这个操作符还是比较简单,但是它后期的运用还是挺多的:
#include
int main()
{
int a = 7;
int b = 3;
int c = a % b;
printf("%d", c);//这里得到的结果为1,1来自于7除3得2余1的1.
return 0;
}
先来看看这些操作符有哪些:
**首先这个地方的移位操作符和位操作符都是操作的二进制位,>>是右移操作符,<<是左移操作符我们还是用代码来解释:
#include
int main()
{
int a=10;//a的二进制表示是00001010.
a=a << 1;//左移后的二进制表示是00010100,右边自动补0.
printf("%d", a);//a的二进制位左移后值变了,10进制对应二进制码0010100是20,所以我们会打印出20
return 0;
}
&符号叫做按位与,它和&&的区别就是&操作的是二进制位,^叫按位异或,只有两边的值相同才得0,两边不同得1,|叫按位或.我们还是用代码来解释:
#include
int main()
{
int a = 10;//a的二进制00001010
int b = 5;//b的二进制 00000101
int c = a & b;//二进制按位只要有1就得1得:00001111
int d = a ^ b;//相同为0,相异为1得:11110000
int e = a | b;//都为1才为1得:00000000
return 0;
}
C语言中的赋值操作符有很多,但是都大同小异,掌握了一个就可以掌握所有!我们来看看有哪些赋值操作符:
我们先来看等号=,它的涵义是将等号右边的值赋值给等号右边的变量,所以我们有时候在if语句中判断真假时很容易写错,比如:
#include
int main()
{
int a=10;
if (a = 1)
{
printf("我要好好学习\n");
}
if (a = 10)
{
printf("我们都要好好学习\n");
}
return 0;
}
上面这段代码我希望它打印出"我们都要好好学习",但是我们运行的时候会两个都打印出来了.这是因为我们的等于=是赋值操作符,括号里a=1的涵义就是把1赋值给a,并不是在判断a和1相不相同,这个地方不管新手还是老手都容易出错,C语言中判断是否相等的操作符是双等号==.我们来把上面的代码修改一下就可以很好的运行了:
#include
int main()
{
int a=10;
if (a == 1)
{
printf("我要好好学习\n");
}
if (a == 10)
{
printf("我们都要好好学习\n");
}
return 0;
}
再来看看操作符+=,我们还是用代码来讲解:
#include
int main()
{
int a=10;
int b = 3;
a += 1;//此代码等同于a=a+1
a += b;//次代码等同于a=a+b
return 0;
}
当我们明白了a+=1等同于a=a+1这件事情的时候,后面的赋值操作符: -=,*=,/= 等等都很好理解了:
#include
int main()
{
int a=10;
int b = 3;
a += 1;//a=a+1
a -= 1;//a=a-1
a *= 2;//a=a*2;
return 0;
}
首先我们先了解一下什么是单目操作符,顾名思义就是这些操作符只作用在一个操作数上,比如我们的算术操作符/,它作用在除号两边的两个操作数,所以它不是单目操作符.
这些单目操作符中,我们把&和*操作符放到后面的章节详细讲解,首先来看看逻辑反操作符 ! 就如它的名字一样,我们在一个变量前面加上一个逻辑反!,它的逻辑就会改变,假如变量原先的逻辑为真(不为0),加了逻辑反!后它的逻辑值就为假了(为0),反之原先它就为假的话,加了逻辑反!后它的逻辑值就为真了
下面的负值-和正值+的意思就是它数学中的意思,所以我们直接来看求变量长度的操作符sizeof,首先它是以字节为单位的,当我们想求一个变量占用的空间时,我们就可以使用sizeof来求得:我们甚至可以用sizeof来求数据类型的大小,如int,char,double:
#include
int main()
{
int a = 10;
char b = 'w';
printf("%d\n", sizeof(a));//大小为4
printf("%d\n", sizeof(b));//1
printf("%d\n", sizeof(int));//4
printf("%d\n", sizeof(char));//1
printf("%d\n", sizeof(double));//8
return 0;
}
紧接着是我们的按二进制位取反~,就像它的名字一样,将一个变量的二进制位取反,将1变成0,将0变成1:
#include
int main()
{
int a = 10;//二进制为:00001010
int c = ~a;//这里的c为:11110101,就是将a的所有二进制为取反得到的
return 0;
}
然后是我们熟悉的自增和自减符号++,–,它们可以作用在操作数的左边或者右边,两种形式表达的意思是不相同的,我们还是用代码给大家解释:
#include
int main()
{
int a = 1;
printf("%d\n", a++);//当++放在操作数右边时,是先使用操作数本来的值再把操作数+1.打印结果为1
printf("%d\n", a);//这个地方,a在前面已经自增过1了,所以打印2
int b = 2;
printf("%d\n", ++b);//当++放在操作数左边时,是先把操作数的值+1再使用+1后的值.打印3
return 0;
}
最后一个单目操作符(类型),指一个括号里面放一个类型,比如(int),(char),它的作用是强制类型转换,我用代码解释:
#include
int main()
{
int a = 5;
int b = 2;
float c = (float)a / b;//我们说当除号左右至少都一个操作数为浮点型,那就是浮点型除法.
int d = a / b;
printf("%lf\n", c);//强制类型转换a的int类型为float类型后,a被当作浮点型,所以进行浮点型除法
printf("%d\n", d);//这里没有强制类型转换,为整型除法.
return 0;
这个操作符很好理解,即大于小于的判断.C语言中的大于等于用的是>=,小于等于用<=.值得注意的是==符号,我们之前说过一个=是用来赋值的,而双等号才是用于判断是否相同的.
我们先来看看条件操作符的使用形式如下:它的意思是如果exp1的逻辑为真,就返回exp2的值,如果exp1的逻辑为假
它的意思是如果exp1的逻辑为真,就返回exp2的值,如果exp1的逻辑为假就返回exp3的值,我们用代码的形式来熟悉一下
#include
int main()
{
int a = 5;
int b = 2;
int c = a > b ? 10 : 5;//这里a大于b为真,所以我们执行冒号前面的值,打印10.
printf("%d", c);
return 0;
}
逗号表达式是由逗号隔开的几个表达式,逗号表达式的结果为最后一个表达式的值,我们直接上代码:
int main()
{
int a = 3;
int b = 2;
int c = 5;
//逗号表达式,是从左向右依次计算的,逗号表达式的结果是最后一个表达式的结果
int d = (a+=3, b=5, c=a+b, c-4);
// a=6 b=5 c=11 7
printf("%d\n", d);//d的值为最后一个表达式的值为7
printf("%d\n", a);//我们再把a,b,c的值打印出来,会发现它们不是本来的3,2,5了,而是在逗号表达式
printf("%d\n", b);//中经过计算后重新得到的数
printf("%d\n", c);
return 0;
}
C语言提供了丰富的关键字,这些关键字都是C语言预先设定好的,用户是不能自己创造关键字的.我们先介绍几个关键字,在后期的学习中如若遇见其他关键字我们再做讲解
typedef 顾名思义是类型定义,这里应该理解为类型重命名。我们直接上代码来帮助理解:
#include
//将unsigned int 重命名为uit, 所以uit也是一个类型名
typedef unsigned int uit;
int main()
{
//观察num1和num2,这两个变量的类型是一样的,相当于把unsigned int改了一个名字
unsigned int num1 = 0;//改名后,改名前的类型也可以使用
uit num2 = 0;
return 0;
}
我们现在学习到的类型int,char长度比较短,使用typedef可能没有那么明显的好处,但是后期我们接触顺序表和链表后,typedef的作用就能体现出来了.
在C语言中:
static是用来修饰变量和函数的
我们要想了解static的功能,就得先引出C语言中内存分配得几大区域,我们大致把内存分为三个区域:栈区,堆区和静态区,我们先不管动态内存区.栈区存放局部变量和函数的形参,静态区存放静态变量和全局变量
我们先来看看没有static修饰时的这段代码会打印什么:
#include
void test()
{
int a = 5;
a++;
printf("%d ", a);
}
int main()
{
int i = 0;
while (i < 10)//调用10次test函数,因为test函数中创建的变量a是局部变量,所以它在我们调用一次函数
{ //结束后会把内存还给系统被销毁,下一次进来又重新定义一个变量a=5,这时的a和前一次调用的a没有关系
test();
i++;
}
return 0;
}
我们可以发现这段代码不断打印6在屏幕上,因为调用一次test函数后a就被销毁了,a++后a变成6后的值不会保存起来,下一次调用函数时a的初始值还是5.我们再来看看用static修饰a后会出现什么变化:
#include
void test()
{
static int a = 5;//static修饰a后,a就不在栈区了,a就被存放在静态区了!
a++;
printf("%d ", a);//会打印6.7.8.9...
}
int main()
{
int i = 0;
while (i < 10)
{
test();
i++;
}
return 0;
}
这里我们先了解static对于局部变量的影响,对于全局变量和函数我们放在<
我们在之前的常量中讲到了define定义的常量,这里就不多再讲
这个地方和定义常量有一点相似,都是将变量替换.我们直接来看代码:
//define定义宏
#define ADD(x, y) ((x)+(y))
#include
int main()
{
int sum = ADD(2, 3);
printf("sum = %d\n", sum);
sum = 10*ADD(2, 3);
printf("sum = %d\n", sum);
return 0;
}
我们可以发现define定义宏和我们之前的函数有一点相似,这里,Add是名字,Add后面括号里的x和y为参数,后面的((x)+(y))叫做宏的实现体,后面的代码我们遇见Add()时就会自动把Add和里面的变量替换为后面的宏的实现体.
C语言中指针就是用来访问内存的,想要了解指针,我们就需要先知道内存,内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。(我们说一个比特位对应一个二进制位也就是存放一个二进制数)
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。
我们可以对比每个大楼有很多房间,我们为了方便知道想去的房间在哪儿,所以我们对房间编号,方便我们寻找房间.
在C语言中,我们的数据存储在内存中,我们为了很方便的找到数据的位置,所以C语言引出了指针和地址的概念,方便我们寻找变量在内存中的位置.变量是创建内存中的(在内存中分配空间的),每个内存单元都有地址,所以变量也是有地址的。
在计算机中有地址线,是物理的电线,它将高低电平信号转换为数字信号(二进制信息):1/0,我们的32位电脑上有32根地址线,每根地址线传达的数字信息要么为0要么为1.所以我们的一个内存单元(一个字节)可以存储二进制00000000000000000000000000000000到11111111111111111111111111111111内的任一数据.转换为10进制也就是存储到4,294,967,296.如果我们的电脑为4位只有4根地址线,那我们的只能存储到2的四次方也就是16.
在写代码之前我们先了解操作符取地址,它的意思就是取出某个变量的地址,我们来定义一个变量再取出它的地址:
#include
int main()
{
int num = 10;
#//取出num的地址
//注:这里num的4个字节,每个字节都有地址,取出的是第一个字节的地址(较小的地址)
printf("%p\n", &num);//打印地址,%p是以地址的形式打印
return 0;
}
这里定义一个整型num,在内存中占用四个字节,那我们说每一个字节(内存单元)都有一个地址,那么这个变量占用四个字节我们变量的地址是哪一个字节的地址呢?
我们现在在vs编译器里输入上面代码后按F11键进行调试,点击F11后点击界面上方的调试->窗口->内存->点击任一内存,跳出窗口后我们输入&num会出现下面的画面:
这里的0x000000D0F7F1FAA4就是我们num的地址,但是我们会发现一个问题,下面的第一行是我们的地址,但是它里面存放的是
0a 00 00 00,按照我们之前的理解它的值是10,转换为二进制应该存储为00000000000000000000000000001010,但是这个地方并不是这样,其实这是因为数据在内存中存放是以16进制的形式存储的,我们知道16进制中的a就是10进制中的10,所以这是等价的(这个地方也能解释之前地址前面的0x是怎么来的,0x代表的就是16进制的意思),那么为什么要在内存中以16进制存储数据呢?刚刚我们也写出了10在二进制中存储的方式我们会发现它很长,有32个数字,如果我们把它转换为16进制形式存储,会简洁很多,我们现在画图来解释一下是怎么从二进制转换为16进制的:
相信讲到这里你可以会有一点懵,但是没有关系,后面我们会不断重复这个内存的存储是什么样的
在我们了解了数据在内存中是怎么存储之后,我们再来讲一讲地址应该如何存储,并且存储后怎么使用
int main()
{
int a = 15;//虽然a占有4个字节,但是当我们&a的时候,拿到的是4个字节中第一个字节的地址
printf("%p\n", &a);
//存放一个整型需要一个整型变量,存放浮点型需要浮点型变量.存放地址我们就需要指针变量
int * pa = &a;//pa存放a的地址的,是一个变量,叫:指针变量,意思是存放指针的变量
*pa = 30;//解引用操作符,*pa 就是a.这里相当于把a的值改成了30.
以我们的整型指针为例,我们可以推广到其他类型:
#include
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'q';//这里*为解引用,pc解引用后就等于变量ch,相当于把ch从字符'w'改为'q'
printf("%c\n", ch);
return 0;
}
我们说整型变量占用四个字节,char类型占用1个字节,double占用8个字节,那么指针变量的大小为多少呢?答案是与你的电脑位数有关,我们可以自己在编译器中打印出指针变量的大小来判断你是多少位的机器:(64位占8个字节,32位占4个字节)
#include
//指针变量的大小取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%d\n", sizeof(char *));
printf("%d\n", sizeof(short *));
printf("%d\n", sizeof(int *));
printf("%d\n", sizeof(double *));
return 0;
}
注意我们不管是指向整型的指针还是指向字符类型的指针的大小都是4/8个字节
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型。
比如我们想描述学生,学生包含: 名字+年龄+性别+学号 这几项信息。如果我们用传统的方法是不能解决的.
这里只能使用结构体来描述了:
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
我们先来用一段代码来认识一下结构体初始化:
struct Stu
{
char name[20];//名字
int age; //年龄
char sex[5]; //性别
char id[15]; //学号
};
int main()
{
//初始化结构体后,name里面放入的是张三,age里面为20,sex为男,id是20180101.中间用逗号隔开.
struct Stu s = { "张三", 20, "男", "20180101" };//这里定义的s是结构体变量,对比int a,这里struct Stu为int,s为a.
//.为结构成员访问操作符
printf("name = %s age = %d sex = %s id = %s\n", s.name, s.age, s.sex, s.id);//这里的.是结构体成员的访问,会一一打印结构体中的信息.
//->操作符相当于结构体中的解引用操作
struct Stu* ps = &s;
printf("name = %s age = %d sex = %s id = %s\n", ps->name, ps->age, ps->sex, ps -> id);//等同于上面的功能
return 0;
}
除此之外,我们还可以在一个main中定义多个结构体并且可以初始化多个结构体变量,比如:
#include
struct Stu
{
//学生的相关属性
char name[20];//名字-字符串,一个汉字是2个字符的
int age;//年龄
char sex[5];//“男”、女、保密
};
struct Book//描述一本书的各方面性质
{
char name[20];//书名
char author[20];//作者
float price;//价格
};
int main()
{
struct Stu s1 = { "张三", 20, "男" };
struct Stu s2 = { "李四", 20, "男" };
struct Stu s3 = { "王五", 20, "男" };
struct Stu s4 = { "翠花", 20, "女" };
struct Book b = { "《C和指针》", "Kenneth Reek", 66.6f };
printf("%s %s %f\n", b.name, b.author, b.price);
return 0;
}
我们还可以将结构体与函数结合起来,利用函数来打印我们的结构体成员:
#include
void printf1(struct Book p)//这里void表示函数没有返回值
{
printf("%s %s %d", p.name, p.author, p.price);//效果和printf1一样
}
void printf2(struct Book* q)//这里用指针来接受形参
{
printf("%s %s %d", q->name, q->author, q->price);//和上面的printf1功能相同
}
struct Book//描述一本书的各方面性质
{
char name[20];//书名
char author[20];//作者
float price;//价格
};
int main()
{
struct Book b = { "《C和指针》", "Kenneth Reek", 66.6f };
printf("%s %s %f\n", b.name, b.author, b.price);
printf1(b);
printf2(&b);//这里传过去b的地址
return 0;
}
这个过程注重给大家解释操作符点.和操作符箭头->都可以访问结构体成员,结构体变量适用. 结构体指针使用-> 如果大家听到这个地方有一点懵逼,没有关系!我们后期还会讲这个板块,详细的讲.
这一章内容十分的多,小编已经写了有一万多字了,我们把C语言所有的内容大概过了一遍,让大家认识一下C语言的各个部分,但是我们说C语言的内容远远不止这一些,我们会在后期的学习中不断为大家分享C语言的各个部分的有深度的内容.如果大家看完这篇文章觉得脑子很懵,这很正常,千万不要放弃,这只是初识C语言,后面我们会不断重复这一章的内容,慢慢熟悉下去!