程序员面试宝典第三版读书笔记1

C/C++中的“按位与”
规则:
1&1=1
1&0=0
0&1=0
0&0=0
按位与运算 按位与运算符“&”是双目运算符。其功能是参与运算的两数各对应的二进位相与。只要对应的二个二进位都为1时,结果位就为1。参与运算的两个数均以补码出现。 例如:3&10可写算式如下: 00000011&00001010 00000010 十进制2
1
2
||   //或操作 a||b a或者b有一个为真时  式子就返回true,都为就假才返回false。
&&  &&且操作 a&&b a和b都为真时  式子才返回 true ,否则就返回 fals


C语言运算符优先级(超详细)   同一优先级的运算符,运算次序由结合方向所决定。

    简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符 > 逗号运算符

EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器

int main()
{
	unsigned char a = 0xA5;
	unsigned char b = ~a >> 4 + 1;
	/*
	C语言运算符优先级(超详细)   同一优先级的运算符,运算次序由结合方向所决定。
	简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符 > 逗号运算符
	
	则~ 优先级大于 +  优先级大于 >>(右移) 
	故先~a,然后>>  (5)   ---也就是先a取反,然后,右移5
	当高级语言转成汇编语言以后,我们可以看到exa是16位寄存器,于是0xA5-0000 0000 1010 0101 
	取反也就是- 1111 1111 0101 1010 ---在右移, 0000 0111 1111 1010
	由于unsigned char型只能表示低8位的数值=250
	*/


	printf("%d\n",b);//250
	system("pause");
	return 0;

}

假设两个数的二进制分别是10101111和10010011
10101111=10000011+00101100
10010011=10000011+00010000
我们把它们拆分成两个数字,第一个是每一位如果相同,取这一位的值,不同的取0;第二个数字是如果这一位相同,取0,如果不同,取这一位的值。
这样两个值相加等于左边的值,这个能理解么?

10000011+10000011=2个(10000011),10000011 and 10000011=10000011,也就是1个(10000011),所以说是一半

右边,因为我们取的不同位,所以如果第一个数字这一位是1,另一边肯定是0,或者相反,不可能都是1,所以
00101100 or 00010000 = 00101100 + 00010000
>>1 相当于除以2(好比10进制,1000移动1位成为100,是除以10)
所以右边也是两数相加的一半。
因为左边右边相加等于原来的数字,所以两边都是一半也就是原来的数字的一半。
好比
a/2+b/2=(a+b)/2

int f(int x, int y)
{
	return ((x&y) + ((x^y) >> 1));
}

int main()
{

	int b = f(729,271);

	printf("%d\n", b);//250
	system("pause");
	return 0;
}
abs是取绝对值。
如果a>b,那么a-b>0,所以表达式就变成了(a+b+a-b)/2=(a+a)/2=a。
如果a<b,那么a-b<0,取绝对值变成-(a-b),所以表达式变成了(a+b-a+b)/2=(b+b)/2=b。

int val = ((a+b) + abs(a-b))/2; // val是 max 

 今天在看C语言的位运算时,看到了如下交换a,b两值的方法:

                    a = a^b;

                    b = a^b;

                    a = a^b;

后来想想,真是太神奇了!其实,我们首先知道,对于任意的x满足:

                               x^x == 0;

                               x^0 == x;

那么上面的式子事实上是利用了如上的规则,首先用a保存了a^b的 值,再用b = a ^ b = (a^b)^b=a^b^b=a^(b^b)=a^0=a,这样就成功的实现了b = a;接着又用a = a^b = (a^b)^b(第一个b还是原来的b,而第二个b已经是a的值,因为前面已经实现了交换)= (a^b)^a = a^a^b = 0^b = b,这样就实现了a = b;于是,就成功的实现了a,b两个值的交换。

#include <iostream>
#include <iomanip>

using namespace std;

class C
{
public:
	C(int i) :m_Count(i){}

	int incr() const
	{
		return ++m_Count;
	}

	int decr() const
	{
		return --m_Count;
	}


private:
	mutable int m_Count;//可以使用mutable关键字修饰变量
};
//注:setw(n) 设域宽为n个字符;setfill('c') 设填充字符为c。

int main()
{
	C c1(0), c2(10);

	for (int tmp = 0,i = 0; i < 10; i++)
	{
		tmp = c1.incr();
		cout << setw(tmp) << setfill(' ') << tmp << endl;

		tmp = c2.decr();
		cout << setw(tmp) << setfill(' ') << tmp << endl;
	}

	system("pause");
	return 0;
}

静态变量是存放在全局数据区的,而sizeof计算栈中分配的大小

指针的大小是个定值,就是4

栈:就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。
堆:就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。
自由存储区:就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。
全局存储区(静态存储区):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后有系统释放。
常量存储区:这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改。

注意sizeof()与strlen()

strlen()的内部实现是用一个循环计算字符串的长度,直到‘\0'为止

strlen()的参数只能是一个char *,且必须是以‘\0'结尾的




#include <iostream>
using namespace std;

void GetMemory(char *p, int num)
{
	p = (char *)malloc(sizeof(char) * num);
}
//第一种采用n级形参改变n-1实参
//一定要用指针参数去申请内存,那么应该采用指向指针的指针,传ptr的地址给函数GetMemory1
void GetMemory1(char **p, int num)
{
	*p = (char *)malloc(sizeof(char) * num);
}
//第二种利用函数返回值来传递动态内存
//
char* GetMemory2(char *p, int num)
{
	p = (char *)malloc(sizeof(char) * num);
	return p;
}
//
void GetMemory22(int *z)
{
	*z = 2;
}
int main()
{
	char *ptr = NULL;

	//GetMemory(ptr,100);程序崩溃,ptr一直指向NULL
	//毛病出在这,void GetMemory(char *p, int num)中*p实际上是主函数中ptr的一个副本,编译器总是要为函数的每个参数制作临时副本
	//本例中,p申请了新的内存,只是把p所指的内存地址改变了,但是ptr丝毫未变 因为GetMemory函数中没有返回值,因此ptr并不会指向p所申请的那段内存
	//事实上,每执行一次GetMemory函数就会申请一块内存,但申请的内存却不能有效释放,结果是内存一直被独占,最终造成内存泄漏
	//GetMemory1(&ptr, 100);



	ptr = GetMemory2(ptr, 100);
	strcpy(ptr, "hello");

	cout << ptr << endl;


	int v;
	GetMemory22(&v);
	cout << v << endl;

	system("pause");
	return 0;
} 

    静态变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0,使用时可以改变其值。

    静态变量或静态函数只有本文件内的代码才能访问它,它的名字在其它文件中不可见。
用法1:函数内部声明的static变量,可作为对象间的一种通信机制
    如果一局部变量被声明为static,那么将只有唯一的一个静态分配的对象,它被用于在该函数的所有调用中表示这个变量。这个对象将只在执行线程第一次到达它的定义使初始化。
用法2:局部静态对象
    对于局部静态对象,构造函数是在控制线程第一次通过该对象的定义时调用。在程序结束时,局部静态对象的析构函数将按照他们被构造的相反顺序逐一调用,没有规定确切时间。
 用法3:静态成员和静态成员函数
  如果一个变量是类的一部分,但却不是该类的各个对象的一部分,它就被成为是一个static静态成员。一个static成员只有唯一的一份副本,而不像常规的非static成员那样在每个对象里各有一份副本。同理,一个需要访问类成员,而不需要针对特定对象去调用的函数,也被称为一个static成员函数。
类的静态成员函数只能访问类的静态成员(变量或函数)。

    进一步详细解释如下:

1.先来介绍它的第一条也是最重要的一条:隐藏

     当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我举例来说明。我们要同时编译两个源文件,一个是a.c,另一个是main.c. 下面是a.c的内容:

char a = 'A'; // global variable

void msg() { printf("Hello\n"); }

    下面是main.c的内容:

int main(void) { 

    extern char a; // extern variable must be declared before use

    printf("%c ", a);

    (void)msg();

    return 0;  }

    程序的运行结果是:

A Hello

    你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。

如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变量,static还有下面两个作用。

2. static的第二个作用是保持变量内容的持久

    存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的。虽然这种用法不常见,但我还是举一个例子。

#include <stdio.h>

 int fun(void){

static int count = 10; // 事实上此赋值语句从来没有执行过

 return count--;

 }

 int count = 1;

int main(void) {

printf("global\t\tlocal static\n");

 for(; count <= 10; ++count)

    printf("%d\t\t%d\n", count, fun());

 return 0; }

    程序的运行结果是:

global local static
1  10
2   9
3   8
4   7
5   6
6   5
7   4
8   3
9   2
10  1

3. static的第三个作用是默认初始化为0.其实全局变量也具备这一属性,因为全局变量也存储在静态数据区

    在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末尾加‘\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’。不妨做个小实验验证一下。

#include <stdio.h>

 int a;

int main(void){

int i;

static char str[10];

printf("integer: %d; string: (begin)%s(end)", a, str);

 return 0;

 }

    程序的运行结果如下integer: 0; string: (begin)(end)

    最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0.

4. 用static声明的函数和变量小结

    static 声明的变量在C语言中有两方面的特征:

1)、变量会被放在程序的全局存储区中,这样可以在下一次调用的时候还可以保持原来的赋值。这一点是它与堆栈变量和堆变量的区别。

2)、变量用static告知编译器,自己仅仅在变量的作用范围内可见。这一点是它与全局变量的区别。

 Tips:
A.若全局变量仅在单个C文件中访问,则可以将这个变量修改为静态全局变量,以降低模块间的耦合度;
B.若全局变量仅由单个函数访问,则可以将这个变量改为该函数的静态局部变量,以降低模块间的耦合度;
C.设计和使用访问动态全局变量、静态全局变量、静态局部变量的函数时,需要考虑重入问题;
D.如果我们需要一个可重入的函数,那么,我们一定要避免函数中使用static变量(这样的函数被称为:带“内部存储器”功能的的函数)
E.函数中必须要使用static变量情况:比如当某函数的返回值为指针类型时,则必须是static的局部变量的地址作为返回值,若为auto类型,则返回为错指针。

    函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

扩展分析:

     术语static有着不寻常的历史.起初,在C中引入关键字static是为了表示退出一个块后仍然存在的局部变量。随后,static在C中有了第二种含义:用来表示不能被其它文件访问的全局变量和函数。为了避免引入新的关键字,所以仍使用static关键字来表示这第二种含义。最后,C++重用了这个关键字,并赋予它与前面不同的第三种含义:表示属于一个类而不是属于此类的任何特定对象的变量和函数(与Java中此关键字的含义相同)。

全局变量、静态全局变量、静态局部变量和局部变量的区别

变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。

(1) 按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。

(2) 按作用域分,  全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,只是程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。

     全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。 而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。

    从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的使用范围。

(1) static 函数与普通函数作用域不同。仅在本文件。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件

(2) static全局变量与普通的全局变量有什么区别:static全局变量只初始化一次,防止在其他文件单元中被引用;

(3) static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
(4) static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝.
(5) 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。

5. C++的static

    C++的static有两种用法:面向过程程序设计的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。

(1)、面向过程设计中的static

1)、静态全局变量

在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。我们先举一个静态全局变量的例子,如下: 

//Example 1

#include <iostream.h>

void fn();

static int n; //定义静态全局变量

void main()

{

 n=20;

 cout<<n<<endl;

 fn();

}

void fn()

{

 n++;

 cout<<n<<endl;

}

    静态全局变量有以下特点:  

 i )  该变量在全局数据区分配内存;
  ii )  未经初始化的静态全局变量会被程序自动初始化为0(自动变量的值是随机的,除非它被显式初始化);   
  iii ) 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;

    静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下图:

代码区

全局数据区

堆区

栈区

  一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。细心的读者可能会发现,Example 1中的代码中将 

 static int n; //定义静态全局变量

改为 

 int n; //定义全局变量

    程序照样正常运行。的确,定义全局变量就可以实现变量在文件中的共享,但定义静态全局变量还有以下好处: 

1) 静态全局变量不能被其它文件所用;  

2) 其它文件中可以定义相同名字的变量,不会发生冲突; 

您可以将上述示例代码改为如下:

//Example 2

//File1

#include <iostream.h>

void fn();

static int n; //定义静态全局变量

void main()

{

 n=20;

 cout<<n<<endl;

 fn();

}

//File2

#include <iostream.h>

extern int n;

void fn()

{

 n++;

 cout<<n<<endl;

}

    编译并运行Example 2,您就会发现上述代码可以分别通过编译,但运行时出现错误。试着将  

static int n; //定义静态全局变量

改为  

int n; //定义全局变量

    再次编译运行程序,细心体会全局变量和静态全局变量的区别。  

(2)、静态局部变量

     在局部变量前,加上关键字static,该变量就被定义成为一个静态局部变量。 我们先举一个静态局部变量的例子,如下: 

//Example 3

#include <iostream.h>

void fn();

void main()

{

 fn();

 fn();

 fn();

}

void fn()

{

 static n=10;

 cout<<n<<endl;

 n++;

}

  通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。但是有时候我们需要在两次调用之间对变量的值进行保存。通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。  静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下

/*
注意char str[] = "hell0"; 与char *str = "hell0";两者的区别
前者分配一个局部数组,对应内存中的栈
后者分配一个全局数组,对应内存中的全局区域
*/
char *strA()
{
	//char str[] = "hell0"; 
	char *str = "hell0";
	return str;
}
//通过static开辟一段静态存储空间
char *strA1()
{
	static char str[] = "hell0111111"; 

	return str;
}
void main()
{
	char *str1 = NULL;

	str1 = strA();
	cout << str1 << endl;

	str1 = strA1();
	cout << str1 << endl;
	system("pause");
}
/*
0000 0000 002A F6F8 0
0000 0000 002A F700 2
p地址0x02AF6F8
q地址0x02AF700 
两个地址相减q-p= (0x02AF700 -0x02AF6F8)=0x08(16进制减法)/sizeof(int)= 8/4=2

*/
void main()
{
	int a[3];
	a[0] = 0, a[1] = 1, a[2] = 2;
	int *p, *q;
	p = a;
	cout << p << " "<< *p << endl;
	q = &a[2];
	cout << q << " " << *q << endl;
	cout << a[q - p] << endl;
	cout << a[*q - *p] << endl;
	system("pause");
}




猜你喜欢

转载自blog.csdn.net/weixin_40807247/article/details/80110329
今日推荐