C基础总结回顾

C基础总结回顾

1、概念

C语言基础大模块:
数据类型
运算符sss
三种程序结构
函数
数组
指针
结构体、公用体、枚举

源文件:源文件即源代码文件,C语言源文件后缀名是.c。
头文件:头文件后缀名为.h(head,头),C语言代码由源文件和头文件组成。
关键字:关键字是C语言征用了的一些字,这些字在C语言中代表特殊含义,已经被C语言定义好了,轮不到我们用了。每个关键字都有自己特定的含义,我们必须搞懂每个关键字的所有含义和用法,否则就看不懂C语言程序。
注释:C语言中注释以//开头,或者/* */,注释是给程序员看的,不是给机器看的。也就是说,程序员写注释给其他人看,以让人明白自己为什么要这么写。但是编译器编译程序的时候是忽略注释内容的,所以机器根本看不见注释,也不理注释。
符号:C语言程序中包含很多符号,如; : , + () {} *等等,各种符号都有自己的含义,必须搞明白各种符号的含义,才能看懂C语言程序。
变量:会变化的量。C语言程序中用变量来进行计算。
函数:函数是C语言的一个基本组成单位,一个C语言程序其实就是由很多函数组成的,每个函数用来完成一定的功能,函数可以调用别的函数来完成功能。函数的标志是().
C语言中有一个特殊的函数叫main,这个函数是整个程序的内定的入口,也就是说整个C语言程序是从main函数开始执行的,其他的函数都是直接或者间接被main调用。

1.1、C语言写代码步骤

第一步:编辑源代码(使用vi或者其他编辑器)
第二步:编译。编译就是用编译器把源程序转化成可执行程序的过程,编译要用到编译器。我们在linux中使用编译器一般是gcc。
譬如:gcc hello.c 把当前目录下hello.c文件编译,得到的可执行文件名字叫a.out也可以自己指定编译后生成的可执行程序的名字,使用gcc hello.c -o hello
第三步:执行编译生成的可执行程序,执行方式是./hello
第四步:调试。当你执行后发现程序结果不对,不是自己想要的,这时候就是返回来看源代码哪里不对。然后修改,再编译执行,再看结果。如此循环直接结果正确。

1.2、VMWare共享文件夹使用

当我们裸机安装了Windows,并且在Windows中安装了虚拟机软件VMWare,并且在虚拟机中安装了ubuntu后。我们一般在Windows中编辑源代码,而在linux中编译、执行源代码。这时候就需要在Windows和linux之间进行交互。
怎么来进行交互呢?之前通过smb服务器,现在通过VMWare的共享文件夹就可以轻松实现。
怎么建立Windows的共享文件夹?
第一步:先在Windows中创建一个文件夹,主要要使用英文名称。
第二步: VMWare中,菜单栏 VM -> Settings -> Options -> Shared Folders选项卡,右边上侧选择Always Enabled,下面点击Add,next,在打开的选项卡中Host Path项目中浏览选择刚才第一步中创建的文件夹,下面Name中会自动弹出一个相同的名字,这个名字是将来Windows中的文件夹在linux虚拟机中的映射文件夹,名字可以改也可以不改。然后一直OK,完成即可。
第三步:在linux中,直接到 /mnt/hgfs目录下,即可找到刚才第二步中Name相同的名字的文件夹,这个目录即是第一步中Windows中目录在linux下的映射。

2、C语言数据类型

2.1、整形
C语言中的整形对应数学中的整数,整形变量是用来描述一个整数值的,整形变量经过计算
后也只能是整数(整型),不可能出现小数(浮点型).

要求:要学会整形变量的定义,赋值,计算; 要学会使用printf函数打印出一些整形变量的值,作为输出和调试。
C语言中整形有三种:
(1)int 整形
(2)short int,又叫做short,短整形(<=整形)
(3)long int,又叫做long,长整型(>=整形)

2.2、浮点型
C语言中浮点型对应数学中的小数。浮点型有float和double两种。使用方式相同,不同在于
表示范围和精度。float表示的范围小,精度低(小数点后6位);而double表示范围大,精度
高。(小数点后16位)
范围是说表示的数有多大,精度是指这个数的分辨率有多细

注意:printf中打印float或double类型,要是用%f,不能用%d。

2.3、字符型
字符型对应ASCII字符。ASCII字符是一种编码,就是用数字编码来表示一个符号的一种方法
本质上说,字符型其实也是整形,只是这些整形数被用来表示一些字符的ASCII编码值,所
以叫做字符型。字符型一般用8位二进制表示,无符号字符型范围是0~255.
字符型其实是一种比short还短的整形,所以它可以和int相运算。
2.4、有符号数和无符号数
数学中数是有符号的,有整数和负数之分。所以计算机中的数据类型也有符号,分为有符号
数和无符号数。

有符号数:
整形:
signed int(简写为 int)
signed long,也写作signed long int,(简写为long)
signed short,也写作signed short int(简写为short)
signed(表示signed int)
浮点型:
signed float(简写为float)
signed double(简写为double)
字符型:
signed char(简写为char)

无符号数:
整形:整形有无符号数,用来表示一些编码编号之类的东西。譬如身份证号,房间号
unsigned int(没有简写)
unsigned long int(简写unsigned long)
unsigned short int(简写为unsigned short)

浮点数:没有无符号浮点数。也就是说,小数一般只用在数学概念中,都是有符号的。
字符型:字符型有无符号数
unsigned char(没有简写)

注意:对于整形和字符型来说,有符号数和无符号数表示的范围是不同的。
譬如字符型,有符号数范围是-128—127,无符号数的范围是0~255

3、C语言常用运算符-数学运算符号

3.1、常见数学运算符号,跟数学中理解相同

+加号
-减号
*乘号
/除号,相除以后的商
% 取余符号,相除以后余数是几
() 括号括起来优先级最高,先计算

3.2、跟数学中意义不同的运算符

= 赋值运算符,与数学中的等号完全不同。赋值运算符作用是经过运算后符号左边的部分(左值,一般是一个变量)的值就等于右边部分(右值,一般是常数或变量)了。
+= : a = a + b; 等同于 a += b;
-= : a = a - b; 等同于 a -= b;
*= : a = a * b; 等同于 a *= b;
/= : a = a / b; 等同于 a /= b;
%= : a = a % b; 等同于 a %= b;

3.3、判断运算符

‘==’ 等于
‘!=’ 不等于
‘>’ 大于
‘<’ 小于
‘>=’ 大于等于
‘<=’ 小于等于

3.4、逗号运算符

, 逗号运算符的主要作用是用来分割

3.5、++与–

a++; 等同于 ++a; 等同于 a = a + 1; 等同于 a += 1;
a- -; 等同于 --a; 等同于 a = a - 1; 等同于 a -= 1;

4、程序结构

介绍

在C语言程序里,一共有三种程序结构:顺序结构、选择结构(分支结构)、循环结构
顺序结构:按照实物本身特性,必须一个接着一个来完成。
选择结构:到某个节点后,会根据一次判断结果来决定之后走哪一个分支。
循环结构:循环结构有一个循环体,循环体是一段代码。对于循环结构来说,关键在于根据判断的结果,来决定循环体执行多少次。
总结:对于顺序结构来说,不需判断,因为下一句指令就是你要执行的。对与循环与选择结构来说,都需要进行判断。然后根据判断结果来决定怎么办。

逻辑上有一种类型,叫bool类型(又写作boolean类型,中文叫布尔类型)。布尔类型只有两个值,真和假。

C语言中的判断运算符:
使用这些判断运算符,可以写出一个判断表达式,这个判断表达式最终的值就是一个bool类型。这个判断表达式的bool值就决定了选择结构如何选择,循环结构如何循环。

4.1、选择结构详解:
第一种:if else

引入关键字:if else else if

if (bool值) // 如果bool值为真,则执行代码段1,否则执行代码段2
{
代码段1
}
else
{
代码段2
}

if (bool值1) // 如果bool值1为真,则执行代码段1
{ // 否则则判断bool值2是否为真,若为真则执行代码段2
代码段1 // 否则直接执行代码段3
}
else if (bool值2) // 开头的if和结尾的else都只能有一个,但是中间的
{ // else if可以有好多个。
代码段2
}
else
{
代码段3
}

第二种:switch case

涉及到的C语言关键字:switch case break default

switch (变量) // 执行到这一句时,变量的值已经知道了
{ // switch case语句执行时,会用该变量的值一次与各个
case 常数1: // case后的常数去对比,试图找到第一个匹配项。
代码段1; // 找到匹配的项目后,就去执行该case对应的代码段
break; // 如果没找到则继续下一个case,直到default。
case 常数2: // 如果前面的case都未匹配,则default匹配。
代码段2;
break;
default:
代码段n;
break;
}

注意:

1、case中必须是常数,而且必须是整形(不能是float double,可以是int char)
2、一般来说,每个case中代码段后都必须有一个break;如果没有,结果可能会让你大吃一惊
3、case之后一般都会有default。语法上允许没有default,但是建议写代码时一定要写。

switch case和if else对比:

1、if else适合对比条件比较复杂,但是分支比较少的情况;switch case适合那种对比条件不复杂,但是分支数很多的情况。
2、所有的选择结构,其实都可以用if else来实现。但是只有部分才可以用switch case实现。
一般的做法是:在适合使用switch case的情况下会优先使用switch case,如果不适合使用switch case,则不得不使用if else。

4.2、C语言中的循环结构
基础知识:

当我们定义了一个局部变量,但是没有初始化的时候,这个值是随机的。

C语言中常用的循环结构有三个:for循环、while循环、do while循环。

4.2.1、for循环

for (循环控制变量初始化; 循环终止条件; 循环控制变量增量)
{
循环体
}

for循环的执行步骤:

1、先进行循环控制变量初始化
2、执行循环终止条件,如果判断结果为真,则进入第3步;如果为假则循环终止,退出。
3、执行循环体。
4、执行循环控制变量增量,转入第2步。

注意:

1、for循环中()中三部分可不可以省略?
标准的for循环,应该把循环控制变量的初始化,增量都放在()当中,并且在循环体中绝对不应该更改循环控制变量(可以引用它的值,但不应该改变它)。

4.2.2、while循环

i = 1;
sum = 0; // 循环初始化
while (i < 100) // 终止条件
{
printf(“i = %d.\n”, i);
sum += i; // 循环体
i += 2; // 循环控制增量,属于循环体的一部分
}

while循环的执行步骤

0、首先是循环初始化。这一部分其实不属于while循环本身。
1、先判断终止条件是否满足。如果是真,则进入第2步;否则直接退出。
2、执行循环体,然后转入第1步。

4.2.3、do while循环

i = 1;
sum = 0; // 初始化条件
do
{
printf(“i = %d.\n”, i);
sum += i;
i += 2; // 增量,循环体的一部分
}while (i < 100); // 终止条件

do while循环的执行步骤

0、首先是循环初始化。这一部分其实不属于do while循环本身。
1、执行循环体(循环控制变量的增量是循环体的一部分)
2、判断终止条件。若成立,则转入1;若不成立则退出

总结:

不管哪种循环结构,都不能缺少一些要素:
循环控制条件初始化,终止条件,循环控制变量增量,循环体。
不同的循环方式(for和while和do while)都有这些,只是格式不同,表现形式不同,放的地方不同,可读性不同,看起来和设计起来难度不同。

while循环和do while循环哪里不同?

while循环是先判断后执行,do while循环是先执行后判断,等循环开始转了之后,其实是一样的。

5、函数

基础知识:

声明周期:指一个东西从出生到死亡的过程。

例:

main:C语言中所谓的主函数,主函数就是一种特别的函数。特别之处在于,一个C语言程序只能有且必须有一个main函数。C语言规定,一个C语言程序从主函数开始执行,到主函数执行完结束。

printf:函数的作用是用来在标准输出中打印信息。这个函数不是程序员自己写的,是C语言标准库提供的一个库函数。在C语言中写代码时可以引用库函数,但是必须使用#include引用这个库函数所在的头文件。

5.1、使用函数来写程序时的关键部分:

函数定义:函数定义是关键,是这个函数的实现。函数定义中包含了函数体,函数体中的代码段,决定了这个函数的功能。

函数声明:函数声明实际上是叫函数原型声明。什么叫原型?函数的原型包含三部分:函数名,返回值类型,函数参数列表。通俗讲,函数原型就是这个函数叫什么,接收什么类型的几个参数,返回一个什么样的返回值。
函数声明的作用,在于告诉使用函数的人,这个函数使用时应该传递给他什么样的参数,它会返回什么样类型的返回值。这些东西都是写函数的人在函数定义中规定好的,如果使用函数的人不参照这个原型来使用,就会出错,结果就会和你想的不一样。

函数调用:函数调用就是使用函数名来调用函数完成功能。调用时必须参照原型给函数传参,然后从函数得到适当的返回值作为结果。

5.2、函数参数:

形参:形式参数的简称。在函数定义和函数声明中的参数列表中的参数,都是形参。
实参:实际参数的简称。函数调用中,实际传递的参数才是实参。

函数调用的过程,其实就是实参传递给形参的一个过程。这个传递实际是一次拷贝。实际参数的时候,实参(本质是一个变量)本身并没有进入到函数内,而是把自己的值复制了一份传给了函数中的形参,在函数中参与运算。这种传参方法,就叫做传值调用。

5.3、返回值:(关键字return)

当函数执行完之后,会给调用该函数的地方返回一个值。这个值的类型就是函数声明中返回值类型,这个值就是函数体中最后一句return xxx;返回的那个值。

5.4、函数名,变量名

1、起名字时候不能随意,要遵守规则。这个规则有两个层次:第一层就是合法,第二层是合理。合法就是符号C语言中变量名的命名规则。合理就是变量名起的好,人一看就知道什么意思,一看就知道这个函数是干嘛的,而且优美、好记。
2、C语言中,所有的符号都是区分大小写的。也就是说abc和Abc和aBc都是不同的符号。
3、C语言函数名变量名的命名习惯。没有固定的结论,有多种使用都很广泛的命名方式。介绍两种:
(1)linux的命名习惯 student_age str_to_int
(2)骆驼命名法 studentAge StrToInt

6、数组

基础知识:

1、在C语言中引用一个单个字符时,应该用单引号’‘括起来,譬如’a’。
2、定义数组同时初始化,则可以省略数组定义时[]中的长度。C语言编译器会自动推论其长度,推论依据是初始化式中初始化元素的个数。由此可知,省略[]中数组元素个数只有一种情况,那就是后面的初始化式必须为完全初始化。
3、在C语言中引用一个字符串时,应该用"“括起来,譬如"abcde”

"abcde"实际上有6个字符,分别是’a’ ‘b’ ‘c’ ‘d’ ‘e’ ‘\0’
‘\0’ 这个字符是ASCII码表中的第一个字符,它的编码值是0,对应的字符是空字符(不可见字符,在屏幕上看不见,没法显示,一般要用转义字符方式来显示。譬如’\n’表示回车符,’\t’表示Tab,’\0’代表空字符)
‘\0’是C语言中定义的字符串的结尾标志。所以,当c语言程序中使用"abcde"这种方式去初始化时,编译器会自动在字符’e’后面添加一个’\0’。于是乎变成了6个字符。

复合数据类型,指由简单数据类型,经过一定的数据结构封装,组成而成的新的数据类型。譬如数组、譬如结构体、譬如公用体

6.1、为什么需要数组?

数组就是数组成一个组,数就是一个特定数据类型的变量,组就是说好多数放在了一起。

6.2、怎么定义数组?

int a[4]; 数组中元素类型 数组名[数组元素个数];

总结:数组中的所有元素必须是同一种数据类型,不可能在一个数组中存储两种数据类型的数。

6.3、怎么使用数组?

数组定义的时候作为整体定义。但是使用的时候不能作为整体使用,使用时必须拆开使用数组中的各个元素。
譬如数组int a[4],使用其中的四个元素,分别用a[0]``a[3],其中[]是数组的标志,[]中的数字叫做数组下标(index,索引),下标是我们访问数组中各个元素的指引。下标是0代表数组中第一个元素,下标是1代表数组第二个元素。如果数组长度为n,下标中最后一个是n-1。访问数组时要特别注意下标,下标是从0开始的,如果下标超出了n-1,会产生越界访问,结果是不可预期的。

6.4、数组的初始化问题

初始化(initinalize,简写为init),是为了让对象有一个预定的初始状态。
譬如说:
(1)简单变量的初始化
当一个局部变量定义时没有初始化,它的值是随机的。这个如果没有注意,可能会导致程序出错。怎么办?解决方案有两个:
第一个,在定义过后明确给它赋值,使用=运算符。
第二个,定义该变量时,同时进行初始化。
总结:
1、一般来讲,只要你记得显示赋值,则两种方式并无优劣差异。但是人会犯错,会不小心,所以还是定义同时初始化好一点,因为这个定义的时候就有了固定值,即使之后忘记显示赋值也不会造成结果是随机的。
2、一般情况下,定义的同时都将变量初始化为0。局部变量定义同时初始化为0,这是一个写代码好习惯。

(2)数组的初始化
第一种:完全初始化。依次赋值
第二种:不完全初始化。初始化式中的值从a[0]开始,依次向后赋值,不足的默认用0填充赋值

6.5、不同数据类型数组
int a[5];			// 整形数组
float a[5];			// 浮点型数组
double a[5];		// 双精度浮点型数组
char a[5];			// 字符数组

程序在环境中运行时,需要一定的资源支持。这些资源包括:CPU(运算能力)、内存等,这些资源一般由运行时环境(一般是操作系统)来提供,譬如我们在linux系统上./a.out运行程序时,linux系统为我们提供了运算能力和内存。
程序越庞大,运行时消耗的资源越多。譬如内存占用,越大的程序,占用的内存越多。占用内存的其中之一,就是我们在程序中定义的变量。
C语言程序中,变量的实质就是内存中的一个格子。当我们定义(创造一个变量)了一个变量后,就相当于在内存中得到了一个格子,这个格子的名字就是变量名,以后访问这个内存格子就使用该变量名就行了。这就是变量的本质。

数据类型的实质是内存中格子的不同种类。譬如在32位机器上
短整形格子(short) 占用2字节空间 16位
整形格子(类型是int)、 占用4字节空间 32位
单精度浮点型格子(float)、 占用4字节空间
双精度浮点型格子(double)、 占用8字节空间 64位
字符型格子(char)。 占用1字节空间 8位

二进制:
二进制位:
字节:等于8个二进制位

6.6、sizeof运算符

作用:返回一个变量或者一个数据类型的内存占用长度,以字节为单位。
sizeof(a)/sizeof(a[0]) 测试一个数组中究竟有多少个元素

6.7、字符数组及它的两种初始化

一种是字符数组,一种是字符指针。

7、指针

7.1、为什么需要指针?
指针存在的目的就是间接访问。
有了指针之后,我们访问变量a不必只通过a这个变量来访问。
而可以通过p = &a; *p = xxx;这样的方式来间接访问变量a。
7.2、两种重要运算符:&和*

&:取地址符,将它加在某个变量前面,则组合后的符号代表这个变量的地址值。
例如: int a; int *p; p = &a; 则将变量a的地址值赋值给p。
就在上面的例子中,有以下一些符号:
a 代表变量a本身
p 代表指针变量p本身
&a 代表变量a的地址值
*p 代表指针变量p所指向的那个变量,也就是变量a
&p 代表指针变量p本身的地址值。符号合法,但对题目无意义
*a 把a看作一个指针,*a表示这个指针所指向的变量。该符号不合法

**:指针符号。指针符号在指针定义和指针操作的时候,解析方法是不同的。
int **p; 定义指针变量p,这里的p含义不是代表指针变量p所指向的那个变量,在定义时这里的含义是告诉编译器p是一个指针。
int p; // p是一个整形变量
int *p; // p是一个指针变量,该指针指向一个整形数
使用指针的时候,*p则代表指针变量p所指向的那个变量。

7.3、指针的定义和初始化

指针既然是一种变量,那么肯定也可以定义,也可以初始化
第一种:先定义再赋值
int *p; // 定义指针变量p
p = &a; // 给p赋值
第二种:定义的同时初始化
int *p = &a; // 效果等同于上面的两句

7.4、各种不同类型的指针

指针变量本质上是一个变量,指针变量的类型属于指针类型。int *p;定义了一个指针类型的变量p,这个p所指向的那个变量是int型。

int *pInt; // pInt是指针变量,指向的变量是int类型
char *pChar; // pChar是指针类型,指向的变量是char类型
float *pFloat;
double *pDouble;
各种指针类型和它们所指向的变量类型必须匹配,否则结果不可预知。

7.5、指针定义的两种理解方法:

int **p;
第一种:首先看到p,这个是变量名;其次,p前面有个’*’,说明这个变量p是一个指针变量;最后,*p前面有一个int,说明这个指针变量p所指向的是一个int型数据。
char ((*pfunc)[])(char *, char *) 类似的复杂表达式,可以用相同的分析方法得到

第二种:首先看到p,这个是变量名;其次,看到p前面的int *,把int *作为一个整体来理解,
int *是一种类型(复合类型),该类型表示一种指向int型数据的指针。

总结:第二种方法便于理解,但是不够本质;建议用第一种方法来理解,因为这种思维过程可以帮我们理解更复杂的表达式。

7.6、指针与数组的初步结合

数组名:做右值时,数组名表示数组的首元素首地址,因此可以直接赋值给指针。
如果有 int a[5];
则 a和&a[0]都表示数组首元素a[0]的首地址。
而&a则表示数组的首地址。

注意:数组首元素的首地址和数组的首地址是不同的。前者是数组元素的地址,而后者是数组整体的地址。两个东西的含义不同,但是数值上是相同的。

根据以上,我们知道可以用一个指针指向数组的第一个元素,这样就可以用间接访问的方式去逐个访问数组中各个元素。这样访问数组就有了两种方式。
有 int a[5]; int *p; p = a;
数组的方式依次访问:a[0] a[1] a[2] a[3] a[4]
指针的方式依次访问:*p *(p+1) *(p+2) *(p+3) *(p+4)

7.7、指针与++ --符号进行运算

指针本身也是一种变量,因此也可以进行运算。但是因为指针变量本身存的是某个其他变量的地址值,因此该值进行* / %等运算是无意义的。两个指针变量相加本身也无意义,相减有意义。指针变量+1,-1是有意义的。+1就代表指针所指向的格子向后挪一格,-1代表指针所指向的格子向前挪一格。

*p++就相当于(p++),p先与++结合,然后p++整体再与结合。
解析:++先跟p结合,但是因为++后置的时候,本身含义就是先运算后增加1(运算指的是p++整体与前面的
进行运算;增加1指的是p+1),所以实际上p++符号整体对外表现的值是p的值,运算完成后p再加1.
所以*p++等同于:*p; p += 1;

++p等同于 p += 1; p;
(p)++,使用()强制将与p结合,只能先计算
p,然后对
p整体的值++。
++(p),先p取值,再前置++,该值+1后作为整个表达式的值。
总结:++符号和指针结合,总共有以上4种情况。- -与++的情况很类似。

7.8、函数传参中使用指针

int add(int a, int b) 函数传参使用了int型数,本身是数值类型。实际调用该函数时,实参将自己拷贝一份,并将拷贝传递给形参进行运算。实参自己实际是不参与的。所以,在函数中,是没法改变实参本身的。

8、结构体、共用体、枚举、宏定义、预处理

基础知识:
1、double float用%f打印,char用%c和%d打印,int用%d,字符串用%s打印,指针用%p打印

8.1、结构体
8.1.1、为什么需要结构体?

没有结构体之前,在C语言中,数据的组织依靠:变量+数组。
最初最简单的时候,只需要使用基本数据类型(int char float double)来定义单个变量,需要几个变量就定义几个。
后来情况变复杂了,有时需要很多意义相关的变量(譬如需要存储及运算一个班级的学生分数)这时候数组出现了。数组解决了需要很多类型相同、意义相关的变量的问题。
但是数组是有限制的。数组最大的不足在于,一个数组只能存储很多个数据类型相同的变量。
所以碰到需要封装几个类型不同的变量的时候,数组就无能为力。
譬如对于题目:使用一个数据结构来保存一个学生的所有信息:姓名 学号 性别
这时候就需要结构体。

8.1.2、什么是结构体?

结构体是一个集合,集合中包含很多个元素,这些元素的数据类型可以相同,也可以不相同。所以结构体是一种数据封装的方法。结构体存在的意义就在于,把很多数据类型不相同的变量封装在一起,组成一个大的新的数据类型。

数据结构:把庞大复杂的数据用一定的方式组织管理起来,便于操作(查找,增加,删除等)这就叫数据结构。

8.1.3、结构体和数组的关联

数组是一种特殊的结构体,特殊之处在于封装内的各个元素类型是相同的。结构体和数组都是对一些子元素的封装,因此定义的时候都是封装作为整体定义,但是使用的时候,都是使用封装中的子元素。一般结构体变量和数组变量都不会作为一个整体操作。

8.1.4、使用结构体的步骤

第一步:定义结构体类型。结构体类型的定义是在函数外面(函数外面 == 全局)的
第二步:使用第一步定义的类型来定义结构体变量。
第三步:使用变量。实际上使用结构体变量的时候,使用的是结构体变量中封装的各个子元素,而不是结构体变量本身。

8.1.5、结构体的初始化

结构体变量和普通变量一样,作为局部变量时,如果定义的时候无初始化也无显式赋值,则结构体变量中的子元素的值是随机的。
发现2种C语言接受的结构体初始化方式。
第一种,完全初始化。{xx, xx, xx, xx, xx};
第二种,部分初始化。
{
.a = xx,
.b = xx,
.c = xx,
.d = xx,
};

新增关键字: struct
新增操作符: .

8.2、共用体(union,联合,联合体)

共用体union在定义和使用形式上,和结构体struct很相似。但是两种数据结构是完全不同的两类东西。
结构体,是对多个数据的组合与封装。
共用体,共用体中只有一个东西,只是它被好几个名字(和类型)共用。

char -128 ~ 127
int -xxxx ~ + xxxx
新增关键字:union

8.3、宏定义

#define N 321 //宏定义的格式
宏定义要注意的问题:
1、宏定义一般是在函数的外面
2、宏定义必须要先定义,再使用宏。如果先使用就会编译报错。
3、宏定义中宏名一般用大写。不是语法规定的,是一般约定俗成的。

为什么使用宏定义?
在C语言中,一般使用常数的时候,都不是直接使用,而是先把该常数定义为一个宏,然后在程序中使用该宏名。这样做的好处是,等我们需要修改这个常数时,只需要在宏定义处修改一次即可。而不用到代码中到处去寻找,看哪里都用过该常数。

8.4、枚举
8.5、预处理

9、C进阶

9.1、数据类型
9.1.1、基本数据类型

数据类型分2类:基本数据类型+复合类型
基本类型:char short int long float double
复合类型:数组 结构体 共用体 类(C语言没有类,C++有)

9.1.1.1、内存占用与sizeof运算符

数据类型就好像一个一个的模子,这个模子实例化出C语言的变量。变量存储在内存中,需要占用一定的内存空间。一个变量占用多少空间是由变量的数据类型决定的。
每种数据类型,在不同的机器平台上占用内存是不同的。我们一般讲的时候都是以32位CPU为默认硬件平台来描述:
char 1字节 8位
short 2字节 16位
int 4字节 32位
long 4字节 32位
float 4字节
double 8字节

9.1.1.2、有符号数和无符号数

对于char short int long等整形类型的数,都分有符号有无符号数。
而对于float和double这种浮点型数来说,只有有符号数,没有无符号数。

对于C语言来说,数(也就是变量)是存储在内存中一个一个的格子中的。存储的时候是以二进制方式存储的。对于有符号数和无符号数来说,存储方式不同的。譬如对于int来说
unsigned int 无符号数,32位(4字节)全部用来存数的内容 所以表示的数的范围
是0 ~ 4294967295(2^32 - 1)
signed int 有符号数,32位中最高位用来存符号(0表示正数,1表示负数),剩余的31位用
来存数据。所以可以表示的数的范围是 -2147483648(2^32) ~ 2147483647(2^31 - 1)

结论:从绝对数值来说,无符号数所表示的范围要大一些。因为有符号数使用1个二进制位来表示正负号。

9.1.1.3、整形数和浮点型数存储方式上的不同

对于float和double这种浮点类型的数,它在内存中的存储方式和整形数不一样。所以float和
int相比,虽然都是4字节,但是在内存中存储的方式完全不同。所以同一个4字节的内存,如果存储时是按照int存放的,取的时候一定要按照int型方式去取。如果存的时候和取的时候理解的方式不同,那数据就完全错了。

备注:详细的数制存储可以查找资料:计算机原码、反码、补码等知识。

总结:存取方式上主要有两种,一种是整形一种是浮点型,这两种存取方式完全不同,没有任何关联,所以是绝对不能随意改变一个变量的存取方式。在整形和浮点型之内,譬如说4种整形char、short、int、long只是范围大小不同而已,存储方式是一模一样的。float和double存储原理是相同的,方式上有差异,导致了能表示的浮点型的范围和精度不同。

9.1.2、空类型(关键字void)

C语言中的void类型,代表任意类型,而不是空的意思。任意类型的意思不是说想变成谁就变成谁,而是说它的类型是未知的,是还没指定的。
void * 是void类型的指针。void类型的指针的含义是:这是一个指针变量,该指针指向一个void类型的数。void类型的数就是说这个数有可能是int,也有可能是float,也有可能是个结构体,哪种类型都有可能,只是我当前不知道。

void型指针的作用就是,程序不知道那个变量的类型,但是程序员自己心里知道。程序员如何知道?当时给这个变量赋值的时候是什么类型,现在取的时候就还是什么类型。这些类型对不对,能否兼容,完全由程序员自己负责。编译器看到void就没办法帮你做类型检查了。

在函数的参数列表和返回值中,void代表的含义是:
一个函数形参列表为void,表示这个函数调用时不需要给它传参。
返回值类型是void,表示这个函数不会返回一个有意义的返回值。所以调用者也不要想着去使用该返回值。

C语言设计基本理念:
C语言相信程序员永远是对的,C语言相信程序员都是高手,C语言赋予了程序员最大的权利。所以C语言的程序员必须自己对程序的对错负责,必须随时脑袋清楚,知道自己在干嘛。

9.1.3、数据类型转换

C语言中有各种数据类型,写程序时需要定义各种类型的变量。这些变量需要参与运算。C语言有一个基本要求就是:不同类型的变量是不能直接运算的。
也就是说,int和float类型的变量不能直接加减等运算。你要运算,必须先把两种类型转成相同的类型才可以。

9.1.3.1、隐式转换

隐式转换就是自动转换,是C语言默认会进行的,不用程序员干涉。
C语言的理念:隐式类型转换默认朝精度更高、范围更大的方向转换。

9.1.3.2、强制类型转换

C语言默认不会这么做,但是程序员我想这么做,所以我强制这么做了。

9.1.4、C语言与bool类型

C语言中原生类型没有bool,C++中有。在C语言中如果需要使用bool类型,可以用int来代替。
很多代码体系中,用以下宏定义来定义真和假
#define TRUE 1
#define FALSE 0

9.2、变量和常量
9.2.1、变量

变量,指的是在程序运行过程中,可以通过代码使它的值改变的量。

9.2.1.1、局部变量

定义在函数中的变量,就叫局部变量。

9.2.1.1.1、普通局部变量(auto)

普通的局部变量定义时直接定义,或者在定义前加auto关键字

void func1(void)
{
int i = 1;
i++;
printf(“i = %d.\n”, i);
}

局部变量i的解析:
在连续三次调用func1中,每次调用时,在进入函数func1后都会创造一个新的变量i,并且给它赋初值1,然后i++时加到2,然后printf输出时输出2.然后func1本次调用结束,结束时同时杀死本次创造的这个i。这就是局部变量i的整个生命周期。
下次再调用该函数func1时,又会重新创造一个i,经历整个程序运算,最终在函数运行完退出时再次被杀死。

9.2.1.1.2、静态局部变量(static)

静态局部变量定义时前面加static关键字。

总结:

1、静态局部变量和普通局部变量不同。静态局部变量也是定义在函数内部的,静态局部变量定义时前面要加static关键字来标识,静态局部变量所在的函数在多调用多次时,只有第一次才经历变量定义和初始化,以后多次在调用时不再定义和初始化,而是维持之前上一次调用时执行后这个变量的值。本次接着来使用。
2、静态局部变量在第一次函数被调用时创造并初始化,但在函数退出时它不死亡,而是保持其值等待函数下一次被调用。下次调用时不再重新创造和初始化该变量,而是直接用上一次留下的值为基础来进行操作。
3、静态局部变量的这种特性,和全局变量非常类似。它们的相同点是都创造和初始化一次,以后调用时值保持上次的不变。不同点在于作用域不同

9.2.1.1.4、register关键字

register(寄存器),C语言的一个关键字
register int i = 3;

总结:

register类型的局部变量表现上和auto是一样的,这东西基本没用,知道就可以了。register被称为:C语言中最快的变量。C语言的运行时环境承诺,会尽量将register类型的变量放到寄存器中去运行(普通的变量是在内存中),所以register类型的变量访问速度会快很多。但是它是有限制的:首先寄存器数目是有限的,所以register类型的变量不能太多;其次register类型变量在数据类型上有限制,譬如你就不能定义double类型的register变量。一般只在内核或者启动代码中,需要反复使用同一个变量这种情况下才会使用register类型变量。

9.2.1.2、全局变量

定义在函数外面的变量,就叫全局变量。

9.2.1.2.1、普通全局变量

普通全局变量就是平时使用的,定义前不加任何修饰词。
普通全局变量可以在各个文件中使用,可以在项目内别的.c文件中被看到,所以要确保不能重名。

9.2.1.2.2、静态全局变量

静态全局变量就是用来解决重名问题的。静态全局变量定义时在定义前加static关键字,
告诉编译器这个变量只在当前本文件内使用,在别的文件中绝对不会使用。这样就不用担心重名问题。所以静态的全局变量就用在我定义这个全局变量并不是为了给别的文件使用,本来就是给我这个文件自己使用的。

9.2.1.1.3、跨文件引用全局变量(extern)

就是说,你在一个程序的多个.c源文件中,可以在一个.c文件中定义全局变量g_a,并且可以在别的另一个.c文件中引用该变量g_a(引用前要声明)

函数和全局变量在C语言中可以跨文件引用,也就是说他们的连接范围是全局的,具有文件连接属性,总之意思就是全局变量和函数是可以跨文件看到的(直接影响就是,我在a.c和b.c中各自定义了一个函数func,名字相同但是内容不同,编译报错。)。

局部变量和全局变量的对比:

1、定义同时没有初始化,则局部变量的值是随机的,而全局变量的值是默认为0.
2、使用范围上:全局变量具有文件作用域,而局部变量只有代码块作用域。
3、生命周期上:全局变量是在程序开始运行之前的初始化阶段就诞生,到整个程序结束退出的时候才死亡;而局部变量在进入局部变量所在的代码块时诞生,在该代码块退出的时候死亡。
4、变量分配位置:全局变量分配在数据段上,而局部变量分配在栈上。

判断一个变量能不能使用,有没有定义,必须注意两点:第一,该变量定义的作用域是否在当前有效,是否包含当前位置;第二,变量必须先定义后使用。所以变量引用一定要在变量定义之前

基本概念:

作用域:起作用的区域,也就是可以工作的范围。
代码块:所谓代码块,就是用{}括起来的一段代码。
数据段:数据段存的是数,像全局变量就是存在数据段的
代码段:存的是程序代码,一般是只读的。
栈(stack):先进后出。C语言中局部变量就分配在栈中。

9.2.2、常量

常量,程序运行过程中不会改变的量。常量的值在程序运行之前初始化的时候给定一次,以后都不会变了,以后一直是这个值。

9.2.2.1、#define定义的常量
#define N 20			// 符号常量
int a[N];
9.2.2.2、const关键字
const int i = 14

const和指针结合,共有4种形式
const int *p; p是一个指针,指针指向一个int型数据。p所指向的是个常量。
int const *p; p是一个指针,指针指向一个int型数据。p所指向的是个常量。
int *const p; p是一个指针,指针指向一个int型数据。p本身是常量,p所指向的是个变量
const int *const p; p是一个指针,指针指向一个int型数据。p本身是常量,指向的也是常量

结论和记忆方法:
1、const在前面,就表示const作用于p所指向的量。所以这时候p所指向的是个常量。
2、const在
后面,表示p本身是常量,但是p指向的不一定是常量。

const型指针有什么用?
char *strcpy(char *dst, const char *src);
字符串处理函数strcpy,它的函数功能是把src指向的字符串,拷贝到dst中。

9.2.2.3、枚举常量

枚举常量是宏定义的一种替代品,在某些情况下会比宏定义好用。
enum

9.3、多文件C语言项目

9.3.1、简单的C语言程序(项目)只有一个C文件(a.c),编译的时候gcc a.c -o a,执行的时候./a
9.3.2、复杂的C语言程序(项目)是由多个C文件构成的。譬如一个项目中包含2个c文件(a.c, b.c),编译的时候 gcc a.c b.c -o ab,执行的时候 ./ab

实验:

在a.c和b.c中分别定义main函数,各自单独编译时没问题;但是两个文件作为一个项目来编译
gcc a.c b.c -o ab的时候,就会报错。multiple definition of `main’
为什么报错?
因为a.c和b.c这时候组成了一个程序,而一个程序必须有且只能有一个main函数。

9.3.3、为什么需要多文件项目?为什么不在一个.c文件中写完所有的功能?

因为一个真正的C语言项目是很复杂的,包含很多个函数,写在一个文件中不利于查找、组织、识别,所以人为的将复杂项目中的很多函数,分成了一个一个的功能模块,然后分开放在不同的.c文件中,于是乎有了多文件项目。
所以,在b.c中定义的一个函数,很可能a.c中就会需要调用。你在任何一个文件中定义的任何一个函数,都有可能被其他任何一个文件中的函数来调用。但是大家最终都是被main函数调用的,有可能是直接调用,也可能是间接调用。

9.3.4、多文件项目中,跨文件调用函数

在调用函数前,要先声明该被调用函数的原型。只要在调用前声明了该函数,那么调用时就好像这个函数是定义在本文件中的函数一样。

总结:

函数使用的三大要素:函数定义、函数声明、函数调用
1、如果没有定义,只有声明和调用:编译时会报连接错误。undefined reference to `func_in_a’
2、如果没有声明,只有定义和调用:编译时一般会报警告,极少数情况下不会报警告。但是最好加上声明。
3、如果没有调用,只有定义和声明:编译时一般会报警告(有一个函数没有使用),有时不会报警告。这时候程序执行不会出错,只是你白白的写了几个函数,而没有使用浪费掉了而已。

实验:

在一个项目的两个.c文件中,分别定义一个名字相同的函数,结果?
编译报错 multiple definition of `func_in_a’
结论:在一个程序中,不管是一个文件内,还是该程序的多个文件内,都不能出现函数名重复的情况,一旦重复,编译器就会报错。主要是因为编译器不知道你调用该函数时到底调用的是哪个函数,编译器在调用函数时是根据函数名来识别不同的函数的。

9.3.5、跨文件的变量引用

(1)通过实验验证得出结论:在a.c中定义的全局变量,在a.c中可以使用,在b.c中不可以直接使用,编译时报错 error: ‘g_a’ undeclared (first use in this function)
(2)想在b.c中使用a.c中定义的全局变量,有一个间接的使用方式。在a.c中写一个函数,然后函数中使用a.c中定义的该全局变量,然后在b.c中先声明函数,再使用函数。即可达到在b.c中间接引用a.c中变量的目的。
(3)想在b.c中直接引用a.c中定义的全局变量g_a,则必须在b.c中引用前先声明g_a,如何声明变量? extern int g_a;

extern关键字:

extern int g_a; 这句话是一个全局变量g_a的声明,这句话告诉编译器,我在外部(程序中
不是本文件的另一个文件)某个地方定义了一个全局变量 int g_a,而且我现在要在这里引用它
告诉你编译器一声,不用报错了。

问题:

1、我只在b.c中声明变量,但是别的文件中根本就定义这个变量,会怎么样?
答案是编译报错(连接错误)undefined reference to `g_b’
2、我在a.c中定义了全局变量g_a,但是b.c中没有声明g_a,引用该变量会怎么样?
答案是直接抱错了,未定义
3、在a.c中定义,在b.c中声明,a.c和b.c中都没有引用该变量,会怎么样?
答案是不会出错。只是白白的定义了一个变量没用,浪费了

结论:

不管是函数还是变量,都有定义、声明、引用三要素。其中,定义是创造这个变量或者函数,声明是向编译器交代它的原型,引用是使用这个变量或函数。所以如果没有定义只有声明和引用,编译时一定会报错。undefined reference to `xxx’

在一个程序里面,一个函数可以定义一次,引用可以有无数次,声明可以有无数次。因为函数定义或者变量的定义实际上是创造了这个函数/变量,所以只能有一次。(多次创造同名的变量会造成变量名重复,冲突;多次创造同名的函数也会造成函数名重名冲突)。声明是告诉编译器变量/函数的原型,在每个引用了这个全局变量/函数的文件之前都要声明该变量/函数

局部变量能不能跨文件使用?

不能。因为局部变量属于代码块作用域。他的作用域只有他定义的那个函数内部。
静态局部变量能不能跨文件使用?
不能。因为本质上还是个局部变量。
讨论跨文件使用问题,只用讨论全局变量和函数就可以了。

9.3.6、头文件的引入
9.3.6.1、为什么需要头文件?

从之前可以看到,函数的声明是很重要的。当我们在一个庞大的项目中,有很多个源文件,每一个源文件中都有很多个函数,并且需要在各个文件中相互穿插引用函数。
怎么解决函数的声明问题?靠头文件。

9.3.6.2、#include包含头文件时,用<>和""的区别

<>用来包含系统自带的头文件,系统自带指的是不是你写的,是编译器或者库函数或者操作系统提供的头文件。
""用来包含项目目录中的头文件,这些一般是我们自己写的。

9.3.6.3、防止重复包含头文件

#ifndef A_H
#define A_H
// C语言头文件中的声明
#endif

9.3.6.4、写程序时,最好不要在头文件中定义变量。因为这时该头文件被多个源文件包含时,就会出现重复定义问题。全局变量的定义就应该放在某个源文件中,然后在别的源文件中使用前是extern声明。

你可能感兴趣的:(C学习笔记,c语言)