c和指针笔记


参考资料:c和指针。

 
 

作用域

作用于分为文件作用域、函数作用域、代码块作用域、原型作用域。

  • 文件作用域(file scope)
    代码块之外的声明的标识符都具有文件作用域,从它们声明之处到源文件结尾处都可以访问。

  • 原型作用域(prototype scope)
    原型作用域只适用于在函数原型中声明的参数名。在原型中参数名不是必须的,但是,如果出现了参数名,你可以随意取个名字。原型作用域防止这些参数名和程序其他部分的名字冲突。
    int funcA(int A);

  • 函数作用域(function scope)
    它只适用于语句标签,语句标签用于goto。

  • 代码块作用域(block scope)
    位于一对花括号之间的所有语句称为一个代码块。任何从代码块开始位置声明的标识符都具有代码块作用域。可嵌套。

 
 

链接属性

链接属性有3中,外部(external),内部(internal),none(无)。

无链接属性的标识符总是被当作单独的个体,也就是该标识符的多个声明被当作独立不同的实体。

属于internal链接属性的标识符在同一源文件内的所有声明中都指同一个实体,但位于不同源文件的多个声明则分属不同的实体。

外部链接属性的标识符无论声明了多少次,位于几个源文件中都表示同一个实体。
 
 

static和extern

关键字static合extern用于在声明中修改标识符的链接属性。
如果某个声明在正常情况下具有external链接属性,在他前面加上static关键字就可以把它变为internal。函数也一样。static只对缺省为external的声明才有效。代码块为加static效果是不一样的。

extern关键字更为复杂。一般而言,它为一个标识符指定external属性,这样就可以在其他任何位置定义这个实体。
 
 

存储类型

变量的储存类型是指存储变量值的内存类型。存储类型决定了变量的生命周期。有三个地方可以存储变量:普通内存,运行时堆栈,硬件寄存器。

变量的缺省存储类型由声明的位置决定。凡是在代码块之外声明的变量总是存储于静态内存中,这类变量称为静态变量。你无法为他们指定其他的存储类型。静态变量是在程序运行之前就创建的,始终存在。

在代码块内声明的变量的缺省存储类型是自动的,也就是它存储与堆栈中,叫自动变量。关键字为auto,默认缺省为auto,所以极少使用。

在代码块内部声明的变量,如果给它加上关键字static,可以使它的存储类型从自动变为静态,但作用域不变。

关键字register可以用于自动变量的声明,提示它们存储于机器的硬件寄存器中,这类变量称为寄存器变量。访问效率比存储于内存的高很多。

静态变量的缺省值为0,自动变量没有缺省的初始值,即默认为垃圾值。
 
 

static

当它用于函数或者代码块之外的变量声明时,static用于修改标识符的链接属性,从external改为internal。但存储类型和作用域不变。
当它用于代码块之内的变量声明时,static用于修改变量的存储类型,从自动变量改为静态变量。但链接属性和作用域不变。

 
 

移位操作符

逻辑位移与算术位移
算术左移和逻辑左移是相同的,都是低位补0。
右移就要看符号位了,如果符号位是1,则移入的位均为1,符号位为0,移入的则均为0。
例如10010110右移两位,逻辑位移为00100101,算术位移为11100101。当操作数为正时则一样。
使用逻辑位移还是算术位移由编译器决定。
 
 

指针

指针表达式

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

*cp++++的优先级是要大于的,这里看上去像是*大于++,实际上的步骤:
1++产生cp的一份拷贝 == 》 temp = cp;
2++操作符增加cp的值 == 》 cp = cp + 1;
3、在cp的拷贝上执行*间接访问操作 ==*temp;

在这里插入图片描述

++*cp,++* 操作符的结合性都是从右到左,所以先执行的是间接访问.
所以这里并不会对cp进行加1,cp指向的地址仍然不变。
*cp = *cp + 1; //'a' + 1 = 'b';

.在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

指针运算

在这里插入图片描述
+1加的是一个指针类型的大小。

指针的算术运算

C的指针只有两种形式:

  • 指针 ± 整数
  • 指针 - 指针

标准允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但不允许对数组第一个元素前的那个内存位置的指针进行比较。(实际上大多数编译器不管)

 
 

函数

真函数

真函数(即返回一个值的函数),不应该在其内部调用一个过程类型的函数(无返回值)

函数的参数

  • 传递给函数的标量参数是传值调用的。
  • 传递给函数的数组参数在行为上就像它们是通过传址调用的那样。

可变参列表

可变参数列表是通过红来实现的,这些宏定义于stdarg.h头文件。这个头文件声明了一个类型va_list和三个宏:va_start、va_arg和va_end。

  • va_start需要一个参数,所以函数至少需要一个命名参数。
  • 这些宏无法判断实际存在的参数个数。
  • 这些宏无法判断每个参数的类型。

 
 

数组

数组名

数组名的值是一个指针常量,第一个数组元素的地址。不要根据这个就得出数组和指针是相同的结论。数组是具有确定数量的元素,而指针只是一个标量值。只有当数组名在表达式中使用时,编译器才会为它产生一个指针常量。

只有两种状态下,数组名并不用常量来表示:当数组名作为sizeof操作符或单目操作符&的操作数时。

下标

这两个表达式是等同的:

  • array[sub]
  • *(array + (sub)) //理解数组和指针转化的基础

2[array]的写法是合法的,换成对等的间接表达式是:
*(2 + (array)) ==> *(array + 2)
虽然合法,但是绝不该这样写,影响可读性。

下标与指针

指针表达式和下标表达式怎么选?
下标具有可读性优势,但可能会影响运行时的效率。
假定两种方法都是正确的,下标绝对不会比指针更有效率,但是指针有时会比下标更有效率。
代码的可读性有时比较重要!

多维数组

matrix[1][5] ⇒ * (*(matrix + 1) + 5) ⇒ *(matrix[1] + 5)

  • 作为参数的多维数组,必须声明维的长度,因为算第二维需要用到,计算下标时。
    eg : int matrix[3][10];
    可以这样写:
    int func(int (*mat)[10]);
    int func(int mat[][10]);
    不能写成:
    int func(int **mat);
    只有一维数组才可以写成指针的形式。而这个例子的mat则是声明为一个指向整形指针的指针,他和指向整形数组的指针并不是一回事。

  • 数组长度的自动计算
    只有第一维才能根据初始化列表缺省地提供,剩余的几个维必须显式的写出。如:

    int matrix[][5] = {
    	{0, 2, 4},
    	{0, 1, 2}
    };
    

指针数组

指针数组的声明,eg:
int *api[10];
由于下标的优先级大于间接访问,所以api是某种类型的数组(包含10个元素),在取得一个数组的元素后,对它进行间接访问,这个表达式不再有其他操作符,所以他的结果是一个整形值。元素类型是指向整形的指针。由于元素是指针,所以这里可以用int **cp = api;

指针数组使用场景,eg:

const char *keyword1[] = {
	"do",
	"for",
	"if",
	NULL
};

const char keyword2[][10] = {
	"do",
	"for",
	"if"
};

第一个声明了指针数组。
在这里插入图片描述
指针数组需要额外的空间保存指针。

第二个则是声明了矩阵。
在这里插入图片描述

结构和联合

高级指针

预处理器

.
.
.
.
.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章