您的位置:首页 > 编程语言 > C语言/C++

C语言深度剖析——个人笔记

2013-05-08 10:01 405 查看
1、const修饰的只读变量不能用来作为定义数组的维数,也不能放在 case关键字后面。

2、#define ENG_PATH_3 E:\English\listen_to_this\listen

\_to_this_3

这里用了 4 个反斜杠,到底哪个是接续符?。注意:反斜杠作为接续符时,在本行其后面不能再有任何字符,空格都不行(前三个反斜杠后面都有字符,这一点是不满足作为连接符的要求的)。所以,只有最后一个反斜杠才是接续符。

3、用define宏定义注释符号?

上面对 define的使用都很简单,再看看下面的例子:

#define BSC //

#define BMC /*

#define EMC */

D),BSC my single-line comment

E),BMC my multi-line comment EMC

D)和 E)都错误,为什么呢?因为注释先于预处理指令被处理,当这两行被展开成//…或/*…*/时,注释已处理完毕,此时再出现//…或/*…*/自然错误.因此,试图用宏开始或结束一段

注释是不行的(也就是说预编译后所得到的这两条注释语句是不会再被处理了,无法达到代码想要的结果)。

4、缺省情况下,编译器默认将结构、栈中的成员数据进行内存对齐。

使用指令#pragma pack (n),编译器将按照 n个字节对齐。

其对齐的规则是,每个成员按其类型的对齐参数(通常是这个类型的大小)和指定对齐参数(这里是 n字节)中较小的一个对齐, 即:min( n, sizeof( item ))。并且结构的长度必须为所用过的所有对齐参数的整数倍,不够就补空字节。

首先,每个成员分别按自己的方式对齐,并能最小化长度。

其次, 复杂类型(如结构)的默认对齐方式是它最长的成员的对齐方式,这样在成员是复杂类型时,可以最小化长度。

然后,对齐后的长度必须是成员中最大的对齐参数的整数倍,这样在处理数组时可以保证每一项都边界对齐。

但是不论类型是什么,对齐的边界一定是 1,2,4,8,16,32,64....中的一个。

5、int a[5];

       当我们定义一个数组 a时,编译器根据指定的元素个数和元素的类型分配确定大小(元素类型大小*元素个数)的一块内存,并把这块内存的名字命名为 a。名字 a一旦与这块内存匹配就不能被改变。a[0],a[1]等为 a的元素,但并非元素的名字。数组的每一个元素都是没有名字的。

       sizeof(a[5])的值在 32位系统下为 4。并没有出错,为什么呢?我们讲过 sizeof是关键字不是函数。函数求值是在运行的时候,而关键字 sizeof求值是在编译的时候。虽然并不存在a[5]这个元素,但是这里也并没有去真正访问 a[5],而是仅仅根据数组元素的类型来确定其值。所以这里使用 a[5]并不会出错。

&a[0]和&a的区别

       这里&a[0]和&a到底有什么区别呢?a[0]是一个元素,a是整个数组,虽然&a[0]和&a的值一样,但其意义不一样。前者是数组首元素的首地址,而后者是数组的首地址。举个例子:湖南的省政府在长沙,而长沙的市政府也在长沙。两个政府都在长沙,但其代表的意义完全不同。这里也是同一个意思。

6、左值与右值的区别(记住,左值和右值是针对表达式而言的。)

“变量和文字常量都有存储区,并且有相关的类型。

区别在于变量是可寻址的(addressable) 对于每一个变量都有两个值与其相关联:

      1.它的数据值,存储在某个内存地址中。有时这个值也被称为对象的右值(rvalue ,读做are-value)。 我们也可认为右值的意思是被读取的值(read value)。 文字常量和变量都可被用作右值。

      2.它的地址值——即存储数据值的那块内存的地址。它有时被称为变量的左值(lvalue,读作ell-value)。 我们也可认为左值的意思是位置值location value。 文字常量不能被用作左值。

总结:

    左值(lvalue) 是指那些单一表达式结束之后依然存在的持久对象。例如: obj,*ptr, prt[index], ++x 都是 lvalue。

    右值(rvalue) 是指那些表达式结束时(在分号处)就不复存在了的临时对象。例如:1729 , x + y , std::string("meow") ,和 x++ 都是 rvalue。

简单解释::++x 和 x++ 的区别的语义上的区别

      当写 int i = 10 ; 时,i 是一个 lvalue,它实际代表一个内存里的地址,是持久的。 表达式 ++x 也是一个 lvalue,它修改了 x 的值,但还是代表原来那个持久对象。但是,表达式 x++ 却是一个 rvalue,它只是拷贝一份x的初值,再修改x的值,最后返回那份临时的拷贝,那份拷贝是临时对象。
++x 和 x++ 都递增i,但 ++x 返回x本身,而 x++ 返回临时拷贝。这就是为什么 ++x 之所以是一个 lvalue,而 x++ 是一个 rvalue。

7、a 和&a的区别

数组可以存任何类型的数据,但不能存函数。

下面再看这个例子:

main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
      对指针进行加 1 操作,得到的是下一个元素的地址,而不是原有地址值直接加 1。所以,一个类型为 T 的指针的移动,以 sizeof(T) 为移动单位。 因此,对上题来说,a 是一个一维数组,数组中有 5 个元素; ptr 是一个 int 型的指针。

      &a + 1: 取数组 a 的首地址,该地址的值加上 sizeof(a) 的值,即 &a + 5*sizeof(int),也就是下一个数组的首地址,显然当前指针已经越过了数组的界限。

      (int *)(&a+1): 则是把上一步计算出来的地址,强制转换为 int * 类型,赋值给 ptr。

      *(a+1): a,&a的值是一样的,但意思不一样,a 是数组首元素的首地址,也就是 a[0]的首地址,&a是数组的首地址,a+1是数组下一元素的首地址,即a[1]的首地址,&a+1是下一个数组的首地址。所以输出 2

      *(ptr-1): 因为 ptr 是指向 a[5],并且 ptr 是 int * 类型,所以 *(ptr-1) 是指向 a[4] ,输出 5。

8、文件 1中定义如下:char a[100];

文件 2 中声明如下 :extern char *a;

当你声明为 extern char *a时,编译器理所当然的认为 a 是一个指针变量,在 32位系统下,占 4 个byte。这 4 个byte里保存了一个地址,这个地址上存的是字符类型数据。虽然在文件 1 中,编译器知道a 是一个数组,但是在文件 2中,编译器并不知道这点。大多数编译器是按文件分别编译的,编译器只按照本文件中声明的类型来处理。所以,虽然a
实际大小为 100个byte,但是在文件 2 中,编译器认为a 只占4 个byte。

注意:以后一定要确认你的代码在一个地方定义为指针,在别的地方也只能声明为指针;在一个的地方定义为数组,在别的地方也只能声明为数组。

9、指针数组和数组指针的内存布局

指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。它是“储存指针的数组”的简称。例如:A),int *p1[10]
数组指针:首先它是一个指针,它指向一个数组。在 32位系统下永远是占 4个字节,至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称。例如:B),int (*p2)[10];(int (*)[10] p2-----也许应该这么定义数组指针,但是为了美观)

10、两位数组赋值问题

intmain(int argc,char * argv[])
{
int a [3][2]={(0,1),(2,3),(4,5)};
int *p;
p=a [0];
printf("%d",p[0]);
}
问打印出来的结果是多少?

很多人都觉得这太简单了,很快就能把答案告诉我:0。不过很可惜,错了。答案应该是 1。如果你也认为是 0,那你实在应该好好看看这个题。花括号里面嵌套的是小括号,而不是花括号! 这里是花括号里面嵌套了逗号表达式! 其实这个赋值就相当于 int a [3][2]={
1, 3,5};所以,在初始化二维数组的时候一定要注意,别不小心把应该用的花括号写成小括号了。

11、无法向函数传递一个数组

C语言中,当一维数组作为函数参数的时候,编译器总是把它解析成一个指向其首元素首地址的指针。

这么做是有原因的。在 C 语言中,所有非数组形式的数据实参均以传值形式(对实参做一份拷贝并传递给被调用的函数,函数不能修改作为实参的实际变量的值,而只能修改传递给它的那份拷贝)调用。然而,如果要拷贝整个数组,无论在空间上还是在时间上,其开销都是非常大的。更重要的是,在绝大部分情况下,你其实并不需要整个数组的拷贝,你只想告诉函数在那一刻对哪个特定的数组感兴趣。这样的话,为了节省时间和空间,提高程序运行的效率,于是就有了上述的规则。同样的,函数的返回值也不能是一个数组,而只能是指针。这里要明确的一个概念就是:函数本身是没有类型的,只有函数的返回值才有类型。

main函数内的变量不是全局变量,而是局部变量,只不过它的生命周期和全局变量一样长而已。全局变量一定是定义在函数外部的。一般是不能把指针变量本身传递给一个函数,只是把指针的一个拷贝传递给函数。

12、栈、堆和静态区

其实堆栈就是栈,而不是堆。堆的英文是 heap;栈的英文是 stack,也翻译为堆栈。

13、只有字符串常量才有结束标志符:chara[8] = “abcdefg”;

下面这种写法就没有结束标志符了:char
a[7] = {‘a’,’b’,’c’,’d’,’e’,’f’,’g’};

14、会产生泄漏的内存就是堆上的内存(这里不讨论资源或句柄等泄漏情况) ,也就是说由malloc系列函数或 new操作符分配的内存。如果用完之后没有及时 free或delete,这块内存就无法释放,直到整个程序终止。

15、使用malloc函数同样要注意这点:如果所申请的内存块大于目前堆上剩余内存块(整块) ,则内存分配会失败,函数返回 NULL。注意这里说的“堆上剩余内存块”不是所有剩余内存块之和,因为
malloc函数申请的是连续的一块内存。

malloc函数的说明文档。申请 0字节内存,函数并不返回 NULL,而是返回一个正常的内存地址。但是你却无法使用这块大小为 0 的内存。这好尺子上的某个刻度,刻度本身并没有长度,只有某两个刻度一起才能量出长度。对于这一点一定要小心,因为这时候
if(NULL !=p)语句校验将不起作用
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: