C与指针笔记(一)

本文介绍了C语言的基础概念,包括多行注释、预处理指令、编译过程、环境区分、数据类型(整型、浮点型、指针)、typedef的应用、常量、作用域、链接属性、存储类型以及static关键字在不同场景下的作用。
摘要由CSDN通过智能技术生成

第一章 基本概念

1.1 简介

1.1.1 注释
    使用 /* 和 */ 将一段代码包围起来可以实现多行注释。这种注释方式可以将一段代码完全忽略,使其不会被编译器或解释器执行。
    使用 /* 和 */ 将一段代码包围起来可以实现多行注释。这种注释方式可以将一段代码完全忽略,使其不会被编译器或解释器执行。
    要在逻辑上删除一段代码,使用预处理指令 #if 指令更好,能够使其在编译时被完全忽略。如下所示:
#if 0
    这是要删除的代码
    可以是多行代码
#endif
1.1.2 预处理指令

#include <stdio.h> 和 #include <stdlib.h> 被称为预处理指令,它们的功能和用途不同。stdio.h 主要用于处理输入和输出操作,封装了getchar()、putchar()、scanf()、printf()、gets()、puts()、sprintf()等函数。而 stdlib.h 则提供了一些与内存、字符串、随机数和程序控制相关的功能,封装了malloc()、calloc()、realloc()、free()、system()、atoi()、atol()、rand()、srand()、exit()等函数。

2.1 环境

ANSI C的实现有两种不同的环境,分别是将源代码转换为机器指令的翻译环境和用于实际执行的执行环境,例如交叉编译器

2.1.1 翻译

翻译阶段由几个步骤组成,一个程序的多个源文件经过编译过程分别转换为目标代码,然后由链接器捆绑在一起形成可执行程序。编译过程也有几个过程,首先是预处理,用实际值替换预处理指令所包含的内容。然后经过解析,判断语句意思,该阶段产生大多数错误和警告信息,随后产生目标代码。优化器会对目标代码进一步处理,使它效率更高。

在大多数UNIX系统中,C编译器被称为cc。cc main.c sort.c look.c编译的文件超过一个时,目标文件不会删除,对文件进行修改后,只对改动过的源文件重新编译。因此可以cc main.o look.o sort.c。也可以编译几个C文件,每个文件都会有一个目标文件cc -c main.c sort.c look.c。链接几个目标文件是:cc main.o sort.o look.o

2.1.2 执行

程序首先要加载到内存中才能执行,通过一个小的启动程序与程序链接在一起,然后调用main函数。程序使用一个运行时堆栈,用于存储函数的局部变量和返回地址,也可以使用静态内存,存储整个执行过程中一直保留的值。

2.2 词法规则

2.2.1 字符

字符集必须包含大写和小写字母以及数字0-9以及常见的一些符号和空格,标准还定义了几个三字母词:??( [, ??' ^。为了在双引号字符串中包含一个双引号,需要使用转义序列,转义序列代表反斜杠后面的那个字符。
在这里插入图片描述

第二章 数据

本章描述变量的三个属性:作用域、链接属性和存储类型。

2.1 基本数据类型

4种基本数据类型:整型,浮点型,指针和聚合类型。

2.1.1 整型

整型包括字符、短整型、整型和长整型,都分为有符号和无符号类型。长整型至少应该和整型一样长,而整型至少应该和短整型一样长,C标准没有规定长整型必须比短整型长,只是不能比短整型短。

char 0~127  signed char -127~127  unsigned char 0~255  short int -32767~32767 unsigned short int 0~65535  int -32767~32767  unsigned int 0~65535  long int  unsigned long int

short int 为16位也就是2字节,long int 至少为32位。至于int是16位还是32位,则由编译器设计者决定。

字面值是字面值常量的缩写,指定了自身的值,并且不允许发生改变。ANSI C允许命名常量,即声明const 的变量,与普通变量类似,但是初始化之后不允许改变值。整型字面值属于哪一种整型取决于是如何书写的,如在数字后加入L,l,U,或u表示long或signed整型。

整数还可以用0开头的八进制,0x开头的十六进制表示。八进制和十六进制的字面值类型可能是int,unsigned int,long或unsigned long类型,在默认情况下,字面值的类型是上述类型中最短且能容纳整个值的类型。字符常量总是int型,不能在后面添加unsigned或long,表现形式是单引号包含的字符。当一个字面值用于确定一个字节中的某些特定位时,写成八进制或十六进制更合适,例如983040写成16进制是0xF000,可以清晰知道高四位是1。

枚举是指它的值为符号常量而不是字面值的类型,这种类型的变量以整型方式存储,这些符号名的实际值都是整型值,从0开始加1递增,有时也可以指定特定整数值。

2.1.2 浮点型

诸如3.14159和6.023×10的23次方的数值无法按照整数存储,从而可以用浮点数存储,通常以一个小数和一个指数组成。浮点数包括float,double和long double,ANSI规定long double至少和double一样长,而double至少和float一样长。float.h定义了FLT_MAX,DBL_MAX和LDBL_MAX表示最大值。浮点数在缺省情况下是double型的。

2.1.3 指针

变量的值存储于内存中,每个内存位置都由地址唯一确定并引用,指针是地址的另一个名字,指针变量是一个值为另一个内存地址的变量。
指针常量与非指针常量在本质上是不同的,因为编译器负责把变量赋值给内存中的位置,因为程序员事先无法知道某个特定变量将存储在内存中哪个位置,因此需要通过操作符来获得变量的地址而不是直接把它的地址写成字面值常量的形式。一个函数每次被调用时,它的局部变量可能每次分配的内存位置都不同,因此把指针常量写成数字字面值的形式几乎没用,C语言没有特地定义该概念。
C语言不存在字符串类型,却存在字符串常量,C语言的字符串概念是一串以NUL字节结尾的0个或多个字符,且通常存储在数组中。在C语言中,表达式语句使用的字符串常量就是这些字符所存储的地址,而不是这些字符本身。因此可以把字符串常量赋值给一个指针,但不能赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。

2.2 基本声明和typedef

使用typedef可以为各种数据类型定义新名字,typedef的声明和普通的声明基本相同:char *ptr2char; 声明了一个指向字符的指针,加上typedef后变为:typedef char *ptr2char;该声明把ptr2char作为指向字符指针类型的新名字。prt2char a;就声明了一个指向字符的指针a。
特点:使用typedef声明类型可以减少使声明变得又臭又长的风险,尤其是复杂的声明。而且如果需要修改程序中所使用的一些数据类型时,仅修改typedef声明比修改所有使用这种类型声明的地方要方便得多。
应该使用typedef而不是#define来创建新建的类型名,因为后者无法正确地处理指针类型。例如:

#define ptr2char char *
ptr2char a, b;

可以正确声明a,但是b却被声明为一个字符。在定义更为复杂的类型名字时,如函数指针或指向数组的指针,使用typedef更合适。

2.3 常量

常量通过const来声明,例如:int const a; const int a;a的值无法被修改但是可以通过初始化对其进行赋值和作为形参时被调用赋值。
int const *pi;是一个指向整型常量的指针,可以修改指针的值,但不能修改所指向的值。int * const pi;是一个指向整型的常量指针,不能修改指针,但能修改指针指向的值。#define也能创建名字常量,如下:

#define NUMBER 50
int const number = 50;

这种情况下使用#define比使用const变量更好,因为只要使用字面值常量的地方均可以用前者,比如声明数组的长度,const变量只能用于允许使用变量的地方。

2.4 作用域(可用范围)

变量在程序中被声明时,只在一定区域能被访问,该区域由标识符作用域决定。编译器可以确认4种不同类型作用域,文件作用域、函数作用域、代码块作用域和原型作用域。标识符声明的位置决定它的作用域。

2.4.1 代码块作用域

位于一对花括号之间的所有语句称为一个代码块,任何在代码块的开始位置声明的标识符都具有代码块作用域,表示它们可以被这个代码块中的所有语句访问。下图中6,7,9,10的变量均具有代码块作用域,函数形参声明5的变量e也具有代码块作用域。
在这里插入图片描述
如果内层代码块和外层代码块有同名标识符,内层标识符将隐藏外层标识符,声明9的f和声明6的f是不同的变量,后者无法在内层代码块中通过名字访问。声明10的i和声明9的i可以共享一个内存地址,因为在任何时刻,两个非嵌套的代码块最多只有一个处于活动状态。

2.4.2 文件作用域

任何在代码块之外声明的标识符都具有文件作用域,它表示这些标识符从它们的声明之处直到它所在的源文件结尾处都可以访问。上图中声明1和2都属于这一类。在文件中定义的函数名也具有文件作用域,因为函数名不属于任何代码块。通过#include包含到其他文件中的头文件声明就像直接写在这些文件中,这些声明的作用域并不局限于头文件的文件尾。

2.4.3 原型作用域

原型作用域只适用于在函数原型中声明的参数名,如上图中的声明3和声明8,在原型中,参数的名字并非必须。但是如果出现参数名,可以取任何名字,不必与函数定义中的形参名匹配。原型作用域防止这些参数名和其他部分的名字冲突,唯一的冲突是在同一个原型中不止一次使用同一个名字。

2.5 链接属性(在文件之间的可用范围)

标识符的链接属性决定如何处理在不同文件中出现的标识符,具有静态持续性的变量,在程序的整个运行周期内有效,具有链接属性。链接属性有external、internal和none,没有链接属性的标识符(none)被当作单独个体,也就是说该标识符的多个声明被当作独立的实体。internal链接属性的标识符在同一个源文件的所有声明都指向同一个实体,在不同源文件的多个声明则分属不同的实体,external链接属性的标识符则不论声明多少次、位于几个文件中都指向同一实体。 下图中b,c,f就是external属性,其余标识符为none,所以如果另一个源文件也包含了b的类似声明并调用c,实际上访问的是这个源文件定义的实体。f的链接属性之所以是external是因为它是个函数,在这源文件中调用函数f,实际上将链接到其他源文件定义的函数。
在这里插入图片描述
关键字extern和static用于在声明中修改标识符的链接属性,如果某个变量声明在正常情况下具有external属性,在前面加上static可以让它的属性变为internal,这个变量就将变为该源文件私有,可以防止被其他源文件调用。但是static只对缺省链接属性为external的声明才有改变链接属性的效果,缺省链接属性为none的声明5前面加上static就不是这样。extern关键字为标识符指定external链接属性,这样就可以访问在其他任何位置定义的这个实体了。当external用于源文件中一个标识符的第1次声明时,指定该标识符具有external属性,但如果用于该标识符第2次或以后的声明时,并不会更改第一次声明时的链接属性。

2.6 存储类型

变量的存储类型是指存储在内存中的类型,有三个地方可以用于存储变量,分别是普通内存,运行时堆栈和硬件寄存器。变量的缺省存储类型取决于它的声明位置,凡是在任何代码块之外声明的变量总是存储与静态内存中,也就是不属于堆栈的内存,这类变量称为静态变量,无法为它们指定其他存储类型,静态变量在程序运行之前创建,在整个执行期间始终存在。
在代码块内部声明的变量缺省存储类型是自动的,也就是存储于堆栈中,称为自动变量。在程序执行到声明自动变量的代码块时,自动变量才被创建,当程序的执行流程离开该代码块时就自行销毁。在代码块内部声明的自动变量前加上static关键字可以让它的存储类型从自动变为静态。函数的形参不能声明为静态,因为实参总是在堆栈中传递给形参,用于支持递归。最后关键字register可以用于自动变量的声明,提示它们应该存储于硬件寄存器而不是内存中,这类变量称为寄存器变量。
静态变量的初始化可以把可执行程序文件想要初始化的值放在执行时变量将会使用的位置。如果不显示地指定其初始值,静态变量将初始化为0。自动变量的初始化需要更多的开销,因为当程序链接时还无法判断自动变量的存储位置。函数的局部变量在函数每次调用时都可能占据不同的位置,基于此,自动变量没有缺省的初始值,而显式初始化将在代码块的起始处插入一条隐式的赋值语句。这条隐式的赋值语句使自动变量在程序执行到所声明的函数时,每次都将重新初始化,这与静态变量不同。除非对自动变量进行显示初始化,否则当自动变量创建时,它们的值是垃圾值。

2.7 static关键字

static用于代码块之外的变量声明时,static关键字用于修改标识符的链接属性,从external改为internal,但标识符的存储类型和作用域不变。当它用于代码块内部的变量声明时,static关键字可以用于修改变量的存储类型,从自动变量修改为静态变量,但变量的链接属性和作用域不受影响。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值