c专家编程笔记

/*
* =====================================================================================
*
*       Filename:  c专家编程笔记.c
*
*    Description:  笔记
*
*        Version:  1.0
*        Created:  2010年12月19日 13时45分03秒
*       Revision:  none
*       Compiler:  gcc
*
*         Author:  Yang Shao Kun (), [email protected]
*        Company:  College of Information Engineering of CDUT
*
* =====================================================================================
*/
foo(const char **p){}
main(int srgc,char **argv)
{
    foo(argv);
}
ANSIC 标准在6.3.22节中讲述约束条件的小节中有这么一句:
    每个实参都应该具有自己的类型,这样它的值就可以赋值给与它所对应的形参类型的对象(该对象的类型不能含有限定符)
    这就是说参数传递过程类是赋值。
所以上面的语句是错误的。

约束条件:
两个操作数都是指向有限定符或无限定符的相关内容类型的指针,左边指针所指向的类
型必须具有右边指针所指向类型的全部限定符。

const char *类型并不是一个有限定符的类型--它的类型是“指向一个具有const限定符的float类型的指针”,也就是说const限定符是修饰指针所指向的类型,而不是指针本身。同样,const char **也是一个没有限定符的指针类型,它的类型是“指向有const限定符的char类型的指针的指针”。

由于char **和const char ** 都是没有限定符的指针类型,但他们所指向的类型不一样(前者是指向 char *,后者是指向 const char *),因此它们是不相容的,类型为char **的实参与类型为const char **的形参是不相容的。

const 和 * 的组合通常只能用于在数组形式的参数中模拟值调用,它声称“我给你一个指向它的指针,但你不能修改它”。

argument #1 imcompatible with prototype

当执行算术运算时,操作数的类型如果不同,就会发生转换,数据类型一般朝着浮点精度更高,长度更长的方向转换,整形数如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。

sizeof ()返回的类型是无符号数。

语言的细节决定了一种语言到底是可靠的还是容易滋生错误的--the details make the difference between a reliable language and an error-prone one.
the definitive account---比较权威的说法。

malloc(strlen (str));这条语句可以断定是错误的,而malloc(strlen(str)+1)才是正确的。这是因为它的字符串处理库函数几乎都包含一个额外的空间,用于容纳字符串结尾的'/0'字符。

分析编程语言缺陷的一种方法是把所有的缺陷归于3类:
不该做的做了--things the language does that it shouldn't do .
少做之过--things it  does't do tha it should.
误做之过--things are completely off the wall.

c 语言中的符号重载

static: 在函数内部,表示该变量的值在各个调用间一直保持延续性。
          在函数这一级,表示该函数只对本文件可见。
extern:用于函数定义,表示全局可见(属于冗余的)
         用于变量,表示它在其他地方定义
void: 作为函数的返回类型,表示不返回任何值。
        在指针声明中,表示通用指针类型。
       位于参数列表中,表示没有参数。
*:乘法运算符。
    用于指针,间接引用。
    在声明中,表示指针。
&:位的AND 操作符
    取地址操作符
=:赋值符
==:比较运算符
<=:小于等于运算符
<<=:左移复合赋值运算符
<:小于运算符
    #include指令的左定界符。

sizeof 的操作数是个类型名的时候,两边必须加上括号,但是操作数如果是变量则不必加括号。

优先级决定一个不含括号的表达式中操作数之间的“紧密程度”。但是,许多操作符的优先级是相同的,这时,操作符的结合性就开始发挥作用了,在表达式中如果有几个优先级相同的操作符,结合性就起仲裁作用。它决定那个操作符优先执行。

例如:
int a,b=1,c=2;
a=b=c; 所有的赋值符(包括复合赋值符)都具有右结合性,就是说,表达式中最右边的操作数最先执行,然后从右到左依次执行。

具有左结合性的操作符("&","|")则是从左到右依次执行。

软件的经济规律显示: 越是在开发周期的早期发现bug,修复它所付出的代价就越小。

在合法的声明中存在限制条件,你不可以像下面那样做:
1:函数的返回值不能是一个函数,所以像foo()(),是非法的。
2:函数的返回值不能是一个数组,所以像foo()[],是非法的。
3:数组里面不能有函数,所以像foo[]这样是非法的。

但是下面的就是合法的:
1:函数的返回值允许是一个函数指针,如:int (*fun())();
2:函数的返回值允许是一个指向数组的指针,如:int (*foo())[];
3:数组里面允许有函数指针,如,int (*foo[])();
4:数组里面允许有其他数组,所以可以经常看见 int foo[][];

结构:就是一种把一些数据项组合在一起的数据结构。
位段的类型必须是:int ,unsigned int 或者是 signed int (或者加上限定符);

参数在传递时首先尽可能的存放到寄存器中,注意,int 型变量i,和只包含一个int型成员的结构变量s在参数传递是可能完全不同,一个int型参数一般会被传递到寄存器中,而结构参数则很可能被传递到堆栈中。

理解c语言声明的优先级规则:
A:声明从它的名字开始读取,然后按照优先级顺序依次读取。
B:优先级从高到低依次是:
    B,1 声明中被括号括起来的那部分。
    B,2 后缀操作符:
        括号()表示这是一个函数,而方括号[]表示这是一个数组。
    B,3 前缀操作符:星号*表示,“指向。。。的指针”。
C:如果const和volatile关键字的后面紧跟类型说明符,那么它的作用于类型说明符,
    在其他情况下,const和volatile关键字作用于它左边紧邻的指针星号。
例如:
char *const *(*next)();
next 是一个指针,它指向一个函数,该函数返回例外的一个指针,该指针指向一个类型为char 的常量指针。

char *(*c[10])(int **p);
c是一个数组,它的元素类型是函数指针,其所指向的函数的返回值是一个指向char的指针。

操作声明器的一些提示:
1:不要在一个 typedef 中放入几个声明器。
2:千万不要把 typedef 嵌到声明的中间部分。

typedef 为数据类型创建别名,而不是创建的数据类型,可以对任何类型进行typedef声明。

typedef 和 #define 的区别:
    1:首先,可以用其他类型说明符对宏类型名进行扩展,但对 typedef 所定义的类型名却不能这样做。
    2:在连续几个变量的声明中,用 typedef 定义的类型能够保证声明中所有的变量均为同一种类型,而用 #define 定义的类型则无法保证。
    例如:
#define int_ptr int *
int_ptr chalk,cheese;
经过宏扩展后:int *chalk,cheese;

/*************************************************************************/
第四章节:the shocking truth: c arrays and pointers are not the same

extern int *x;
extern int y[];//由于没有在声明中为数组分配内存,所以并不需要提供关于数组长度的信息。对于多维数组,需要提供除最左边的一维之外的其他维的长度。
第一条语句声明x是个int 型的指针,第二条语句声明y是个int 型数组,长度尚未确定(不完整的类型),其储存在别处定义。

c语言中的对象必须有且仅有一个定义,但它可以有多个extern 声明。

定义是一种特殊的声明,它创建了一个对象,声明简单的说明了在其他地方创建的对象的名字,它允许你使用这个名字。

定义:只能出现在一个地方,确定对象的类型并分配内存,用于创建新的对象。
声明:可以多次出现。描述对象的类型,用于指代其他地方定义的对象。

定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时同时赋给指针一个字符串常量进行初始化。
注意:只有对字符串常量才是如此,不能指望为浮点数之类的常量分配空间。

/******/
笔记不知道在那个地方去了,估计是弄丢了,悲剧,北吹的哼哦!

/*******************************************************************/
继续第六章的笔记,

setjmp(jmp_buf j),必须首先被调用,它表示:"使用变量j记录现在的位置,函数返回0"
longjmp(mup_buf j,int i),可以接着被调用,表示,返回j所记录的位置,让它看上去是从原先的 setjmp()函数返回一样,但是函数返回 i,使代码能够知道它是实际上是通过lognjmp 返回的。

需要注意的地方是: 保证局部变量在longjmp过程中一直保持它的值的唯一可靠的方法是把它声明为 volatile  。

/*******************************************************************/
第七章:thanks for merrory

内存地址的形成经过是:取得段寄存器的值,左移4位,然后就是16位的偏移地址,它表示段内的地址,如果把段寄存器的值加上偏移地址,就得到最终的地址。

intel 8086 处理器的内存地址是通过组合16位的段地址和16位的偏移地址形成的。在加上偏移地址前,段地址要左移4为。

所有的磁盘制造商都是使用十进制数而不是二进制数来表示磁盘的容量。

虚拟内存通过“页”的形式组织,页就是操作系统在磁盘和内存之间移来移去或进行保护的单位。

进程只能操作位于物理内存中的页面。
行:就是对cache进行访问的单位,每行由两部分组成:一个数据部分以及一个标签。
块:一个cache行内数据被称作块,块保存来回移动与cache行和内存之间的字节数据。

堆中的所有东西都是匿名的----不能按名字直接访问,只能通过指针间接访问。
堆的末端由一个称为break 的指针来标识,当堆管理器需要更多的内存时,它可以通过系统调用brk和sbrk来移动break指针。

如何检测内存泄漏:(好像这两个步骤在我的fedora机器上面没有实现,估计是作者用的系统是sun公司的)
    首先:使用swap 命名观察还有多少可用的交换空间。

    其次:用ps -lu 用户名来显示所有的进程的大小。

/***********************************************************/
第八章:

printf("%d",sizeof 'a');
这行代码打印出来的值是:4.
在表达是中,每个char 都被转换为 int 注意所有位于表达式中的 float 都被转换为
double 由于函数参数也是一个表达式,所以当参数传递给函数时也会发生类型转换,具体的说: char 和 short 转换为 int ,而 float 转换为 double。
这个特性被称为:类型提升。
整形提升:就是 char short int 和位段类型,以及枚举类型将被提升为 int ,前提是 int 能够完整的容纳原先的数据,否则 将被转换为 unsigned int。ANSI C 表示如果编译器能够保证运算结果一致,也可以不用类型提升。

函数从堆栈中取出的参数总是 int 类型,并在printf 或其他被调用函数里按统一的格式处理。

如果一个库函数调用或系统调用遇到问题,它将会设置 errno 的值以提示问题的原因,然而 只有当确实出现问题的时候,errno的值才是有效的--库函数或系统调用会使用某种方法来指示这一点。

复杂的类型转换可以按下面的3个步骤编写:
1:一个对象的声明,它的类型就是想要转换的结果类型。
2:删去标识符(以及 任何如extern 之类的储存限定符),并把剩余的内容放在一对括号里面。
3:把第二步产生的内容放在需要进行类型转换的对象的左边。

/***********************************************************************/
第九章:more about arrays
什么时候数组和指针是相同的:
规则1:表达式中的数组名被编译器当作一个指向该数组第一个元素的指针。
规则2:下标总是与指针的偏移量相同。
规则3:在函数参数的声明中,数组名是被编译器当作指向该数组第一个元素的指针。

c语言把数组下标改写成指针偏移量的更本原因是指针和偏移量是底层硬件所使用的几绷模型。

parameter:is a variable defined in function definition or a function prototype declaration ,some people call this "formal parameter".
argument: is a value used in particular call to a function ,some people call this is a "actual parameter".

标准规定作为“类型的数组”的形参的声明应该调整为“类型的指针”,在函数形参定义这个特殊的情况下,编译器必须把数组形式改写成指向数组第一个元素的指针形式。编译器只向函数传递数组的地址,而不是整个数组的拷贝。----出于效率的考虑。

在c语言中,所有的非数组形式的数据 argument 均以传值形式(a copy of argument is made and passed to the called function ; the function  can't change the value of actual variable used as an argument,just the value of the copy it has )调用。

数组名是不可修改的左值,只能通过指针来修改。

通过以上,知道,数组参数的地址和数组首个元素的地址是不一样的,因为在编译器处理的时候,数组是作为指针来使用的,该指针保存的是数组第一个元素的地址。

一下是写的一个程序,运行后的结果,就是书中的程序:

这个是主函数中的结果:
addr of global array = 0x8049830
addr (ga[0])=0x8049830
addr (ga[1])=0x8049831

函数调用的结果:数组形式调用
addr fo array param =0xbfe4a350
addr (ca[0])= 0x8049830
addr (ca[1])= 0x8049831
++ca= 0x8049831
函数调用的结果:指针调用的形式,可以看出,两个调用的结果是完全一样的,推理,编译器的处理是一样的。
addr of ptr param =0xbfe4a350
adr (pa[0])=0x8049830
adr (pa[1])=0x8049831
++pa=0x8049831

/*******************************************************************/
第十章:more about the pointers
数组名被改写成一个指针参数,规则并不是递归定义的,数组的数组会被改写为“数组的指针”,而不是“指针的指针”。

比如:
    argument                                        所匹配的形式参数
数组的数组 char c[8][10]                             char (*c)[10]  数组指针
指针数组     char *c[10]                              char **c      指针的指针 数组指针     char (*c)[64]                             char (*c)[64] 不改变
指针的指针 char **c                                 char **c      不改变

函数不能返回一个数组,但是可以返回指向数组的指针,或结构体中包含有数组的类型,但是,返回的不能是局部变量。

/************************************************/
第十一章:you know c,so c++ is easy

面向对象编程很自然地把对象作为程序设计的中心主题,软件对象的定义有很多种,其中绝大多数定义都同意面向对象的关键就是把一些数据和对这些数据进行操作的代码组合在一起,并用某种时髦的手法将它们作成一个单元----我们称为class。

面向对象编程的特点是继承和动态绑定,c++通过类的派生支持继承,通过虚拟函数支持动态绑定,虚拟函数提供了一种封装类体系实现细节的方法。

面向对象编程的关键概念:
1:抽象abstraction:它是一个去除对象中不重要的细节过程,只有那些描述了对象的本质关节点才被保留。---对事物的简化而已。
2:类class :类是一种用户定义类型,就好像是 int 这样的内置类型一样。类机制也必须允许程序员规定他所做定义的类能够进行的操作。你必须声明该类的变量以便进行有用的工作。例如:作为参数传递,作为函数返回值。其实,类就是用户定义类型加上所有对该类型进行的操作。
3:对象object:某个类的一个特定变量。
4:封装encapsulation:把类型,数据和函数组合在一起,组成一个类,在c语言中,头文件就是一个脆肉的封装实例。
5:继承inheritance:允许类从一个更简单的基类中接收数据结构和函数,派生类获得基类的数据和操作,并可以根据需要对它们进行改写,也可以在派生类中真加新的数据和函数成员。记住,继承是在两个类之间进行,而不是两个函数之间。

c++中的类修饰符:
1:friend 属于friend的函数不属于类的成员函数,但可以像成员函数一样访问类的 private和protected成员。friend 可以是一个函数,也可以是一个类。

2:vitual:

::被称为“全局范围分解符”,跟在它前面的标识符就是进行查找的范围,如果::前面没有标识符,就表示查找范围为全局范围。

每个成员函数都有一个 this 指针参数,它是隐式赋给该函数的,它允许对象在成员函数内部引用对象本身。

绝大多数类至少具有一个构造函数,当类的一个对象被创建时,构造函数被隐式的调用,它负责对象的初始化,与之相应,类也存在一个清理函数,称为析构函数,当对象被销毁时(超出其生存范围或进行delete操作,回收它所使用的堆内容),时,析够函数被自动调用。

在成员函数前面加上 virtual 关键字告诉编译器该成员是多态的,也就是虚拟函数。
多态:是指一个函数或操作符指有一个名字,但它可以用于几个不同的派生类型的能力,每个对象都实现该操作的一种变型,表现一种最适合自身的行为。
多态是一种运行时的效果,它是指c++ 对象在运行时决定应该调用那个函数来实现某个特定操作的过程。

内联函数:程序员可以规定某个特定的函数在行内以指令流的形式展开(就像宏一样),而不是一个函数调用。

new 和delete 操作符: 用于取代malloc ()和 free()函数,new能真正建立一个对象,则malloc()函数只是分配内存。

尽量使用的c++特性:
类,构造函数和析够函数但只限于函数体非常简单的例子
重载,包括操作符重载和I/O,但重继承和多态。
文件描述符就是开放文件的每个进程表中的一个偏移量。用于标识文件。
file 指针保存了一个file结构的地址,file结构用于表示开放的i/o流。

你可能感兴趣的:(编程,c,Arrays,语言,编译器,Pointers)