null

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

面试宝典第三版阅读笔记

1.printf的打印顺序从右到左

#include<stdio.h>

int main()
{
    int arr[]={6,7,8,9,10};
    int *ptr=arr;
    printf("%d,%d",*ptr,*(++ptr));
    return 0;
}

结果是7,7,原因在于printf函数打印的顺序是从右到左,先对ptr指针加1指向7,然后再进行打印。

2011/4/30日晚20:55分补充:这道题太经典了,远没有前面写的那么简单~通过北庚进一步升华,此题的扩展题如下:

#include <iostream>
using namespace std;

void f(int a,int b)
{
	cout<<a<<endl<<b<<endl;
}

void main()
{
	int arr[]={6,7,8,9,10};
	int *ptr=arr;
	*(ptr++)+=123;
	f(*ptr,*(++ptr));	
}

结果是8,8;而不是7,8或者7,7.

原因在于无论是什么函数在被调用的时候,在压栈的过程中,先将参数按照参数表相反的顺序来进行压栈,如扩展题中先将b压栈,再将a压栈,然后再压入返回地址。在栈中压栈操作是从高地址向低地址方向进行,即b在栈中的地址(%ebp+8)>a的地址(%ebp+4)>返回地址(%ebp). 多亏学习了深入理解计算机系统那本书!

printf也是一个函数,不管具体实现的话,为什么从右向左进行,原因是一样的。向北庚继续学习~~

2.局部变量的定义和全局变量覆盖

#include <iostream>

using namespace std;

int i=1;

int main()
{
    int i=i;
    return 0;
}

主要会输出,定义局部变量 i 以后,没被初始化就直接使用。、

首先会注意到一个全局变量i,然后main里面的局部变量 i 在定义以后就会将值为1的全局变量 i 覆盖,在main函数的等号右边的 i 实际上main函数里面定义的局部变量。运行当然没错误,但是如果用输出警告信息的话将看到

3.当在C++中用cout输出bool类型数据

#include <iostream>

using namespace std;

int main()
{
    int y;
    int i=y=1;
    bool j=true;
    cout<<(i==y)<<endl;
    cout<<j<<endl;
    return 0;
}

结果是1 1,原来用C#多了,想着会输出true呢。cout会自动识别输出类型,上面程序给了两种形式的true输出。

现在有个问题在C中bool变量是如何实现的呢?以前经常用如下方法定义:

enum{true,false}; //法1,用枚举变量
#define TRUE 1     //法2,用宏定义
#define FALSE 0

现在GCC已经对此进行了扩展,可以引入stdbool.h文件来使用定义好的bool类型,定义如下:

可以用_Bool或者bool直接使用

#include<stdio.h>
#include<stdbool.h>

void main(void)
{
    _Bool a=0;
    _Bool b=999;
    printf("%d,%d",a,b);
}

输出结果为0,1;

4.在if语句条件判断语句condition中,a如果是一个变量的话,下面condition的两种书写方式哪个好

(1)'A'==a

(2)a=='A'

应该是第一种方式更好!原因是在if(condition)中若选择第二种方式,少写一个等号的话(a='A')则会被编译器理解成为对变量a进行赋值操作,编译器检查不出来的逻辑错误。如果是第一种方式,编译器能够检查出来语法错误,因为不能对常量进行赋值。

5.C语言格式化输出:

    cout<<hex<<16<<endl;
    cout<<16<<endl;

结果是10,10,而不是想象中的10,16. 林桐认为hex十六进制格式化输入输出就相当于一个开关,不需要的话要关闭(暂时没找到)。或者直接转换为dec十进制输出。

    cout<<hex<<16<<endl;
    cout<<dec<<16<<endl;

输出格式说明请参考文章:http://blog.csdn.net/angelcm51/archive/2008/04/26/2330496.aspx

6.类型转换

第一题:

#include <iostream>

using namespace std;

int main()
{
    float a=1.0f;
    cout<<(int)a<<endl;
    cout<<&a<<endl;
    cout<<hex<<(int&)a<<endl;
    cout<<boolalpha<<((int)a==(int&)a)<<endl;
    return 0;
}

输出结果为:

分析:

(int )a 表示将float类型数据强制转化为int型数据,因为是整数1,所以转换后的大小没有精度上的变化,结果还是1.

&a表示输出变量a的地址,这个不用多说。

(int&)a为什么是0x3f800000(这里是按照16进制输出的,十进制数的大小是1065353216)呢?

因为浮点数在计算机内部的表示形式与int类型的整形数不同,按照IEEE浮点表示单精度浮点格式(C语言中float)s(符号sign),exp(指数exponent),frac(尾数)域分别是1位,8位,23位;对于双精度浮点格式(C语言中的double)三个域分别是1位,11位,52位。(参考《深入理解计算机系统》一书,中文版,P69页)

这里a对应的内存中的浮点表示应该为0  01111111  00000000000000000000000

(int&)表示直接得到a在内存单元的值并且按照int型数据表示出来,即为(00111111100000000000000000000000),将二进制数表示为十六进制就是0x3f800000,表示为十进制数是1065353216

boolalpha表示按照bool类型的true或者false表示,若不加则用1表示true或者0表示false。

如果将float a=0.0f的话,由于0扩展前后都是0,大小不变所以最后输出结果是true。

第二题:

#include<stdio.h>
using namespace std;

int main()
{
    unsigned int a=0xfffffff7;
    unsigned char i=(unsigned char)a;
    char* b=(char*)&a;
    printf("%08x,%08x",i,*b);
    return 0;
}

结果如下:

解析如下:

(unsigned char)a对a进行数据截断,i=0xf7,
char* b=(char*)&a表示将a的地址强制转换为char*类型的指针,并赋给char*类型指针b,这里将int型地址(其实相当于int*型的指针)转化为char*类型指针,
指针内容不会发生任何变化,唯一的区别是int*型指针加1相当于地址值加4,char*型指针加1相当于地址值加1. 这点与将int类型数据转化为char类型数据不同。

补充:数据在计算机中都是以补码形式存在的,正数进行位扩展的话是零扩展,负数进行位扩展的话是符号扩展,即在符号位后不足的位数补上最高有效位的值。

(《深入理解计算机系统》中文版,P50页)

又想起前几天和北庚说的一题:

#include<stdio.h>

int main()
{
    char a=0xA5;
    printf("结果:%d\n",a);
    return 0;
}

结果如下:

结果分析如下:

打印时用%d表示按照整形类型进行打印,

char类型的a转化成二进制是1010 0101,打印的时候相当于对char类型的a进行类型提升到int类型,由于a的首位是1表示负数,所以提升的时候是按照符号扩展来的,相当于a提升后的数据为1  111111111111111111111111(24个1)   010 0101,转化为十进制数为-91,转化为十六进制数结果为ffffffa5

上面程序等同于

#include<stdio.h>

using namespace std;

int main()
{
    char a=0xA5;
    int i = (int)a;
    printf("十进制输出:%d\n",i);
    printf("十六进制输出:%x\n",i);
    return 0;
}

输出结果如下:

PS:强烈推荐深入理解计算机系统(Computer Systems A Programmer's Perspective),找工作必备良书~~~

7.宏定义

(1)宏定义FIND求一个结构体struc里某个变量相对struc的偏移量

#include <iostream>
#define FIND(struc,e) (size_t)&(((struc*)0)->e)
using namespace std;

struct aa
{
    public:
    int a;
    char b[20];
    double c;
};
int main(void)
{
    cout<<FIND(aa,a)<<endl;
    cout<<FIND(aa,b)<<endl;
    cout<<FIND(aa,c)<<endl;
    return 1;
}

#define FIND(struc,e)  (size_t)&(((struc*)0)->e)解释:

(struc*)0将0强制转化为struc*类型指针所这项的地址,即struc*所指向的地址为0.

((Struc*)0)->e表示结构体内e元素。前面加上&表示取地址,加上(size_t)表示转化为十进制数,若不加size_t表示出来就是十六进制数。

(2)宏定义1年有多少秒

#define SECONDS_PER_YEAR  (60*60*24*365)UL

亮点:这个表达式会使16位机溢出,所以加上L表示长整形数,加上UL表示无符号的长整形就更好了!

--------------------------------------------------------------------------------------------------------------------------------------------------

Const作用:1.定义const常量2.可以修饰函数的参数和返回值 3. 可以定义const *和*const

Const与define区别:都可以定义常量。Const好处:1. const常量有数据类型,编译器可以对其进行类型安全检查,define只是进行字符替换且没有类型安全检查。2. 调试工具可以对const常量进行调试但不能对宏常量进行调试。建议:C++中只用const常量不用宏常量,即用const完全代替宏常量。

8.sizeof的基本问题

对齐什么的就不说了。

在C++中引入头文件<string.h>以后,声明string a;那么sizeof(a)=4,我认为这里的sizeof相当于一个char*指针。

不管什么类型的指针大小都是机器全字长,32位机上为4个字节。long int也是机器全字长,32位机上是4个字节。

sizeof(unsigned int)和sizeof(int)的大小是一样的,unsigned影响的只是最高位bit(判断正或负),不影响大小。

long double 是12个字节,double是8字节。

long和short默认为long int和short int,分别是4个字节和2个字节

char ss[100]="0123456789";

sizeof(ss)为100,表示内存中预分配的大小,100*1;

strlen(ss)为10,它的内部实现实际上是一个循环计算字符串的长度,知道'\0'为止。

9.C++中有了malloc/free, 为什么还需要new/delete

malloc/free是C/C++中的标准库函数,new/delete是C++中的运算符而不是库函数。都用于申请和分配内存。

对于非内部数据类型的对象来说,只用malloc/free无法满足动态创建对象的需要。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够执行构造函数和析构函数的任务,这些任务由new/delete来完成

10.const指针和指向const对象的指针。

int *const p;--------p是const指针,该指针指向的对象不能够改变,可以改变内容;

const int *p:--------p是指向const对象的指针,该指针指向的对象可以改变,但是内容不可以改变。

11.易忘小知识点

(1)引用不能为空且必须初始化,即不能写成(&temp;);

(2)const常量赋值时必须同时初始化。 不能写成 const double temp;。

12.良好的C++中delete习惯。

一般是在删除指针p后,将指针赋值为null或者0,如(delete p; p=NULL;)

示例:

#include<iostream>
using namespace std;
typedef unsigned short int USHORT;
int main()
{
   USHORT *p=new USHORT;
   *p=10;
   cout<<hex<<"p"<<*p<<endl;
   delete p;
   //p=NULL;
   long *plong=new long;
   *plong=90000;
   cout<<"plong:"<<*plong<<endl;
   *p=20;
   cout<<"p"<<*p<<endl;
   cout<<"plong:"<<*plong<<endl;
}

两次打印的plong结果不同为什么,结果按16进制打印?

delete p;表示释放p指向的内存地址中的内容,但p仍然指向那个地址,在后面又用到了p,p=0x0014,将原来plong后面的一个字节给覆盖了(plong原来是0x 0001 5f90,现在变成了0x 0001 0014)。

13.关于句柄和指针(参考下面两个博客就行了)

http://www.cppblog.com/mymsdn/archive/2009/02/19/handle-in-windows.html

http://blog.csdn.net/upwaker/archive/2004/07/04/33827.aspx

14.关于auto_ptr的使用

先看下面代码:

#include<iostream>
#include<memory>
using namespace std;

class A
{
    public:
    A(){};
    ~A()
    {
        cout<<"~A"<<endl;
    };
    void show()
    {
        cout<<"hello world"<<endl;
    }
};
int main()
{
    //1.auto_ptr
    auto_ptr<A> a(new A());
    a->show();
    //2.直接声明A b
    //A b;
    //b.show();
    //3.声明一个A*类型的指针,new一个新对象
    //A *c=new A();
    //c->show();
}

在1运行的时候,使用了一个auto_ptr智能指针a,不用自己手动delete,auto_ptr 类是一个模板类,它被定义在 memory 头文件中。auto_ptr不能当做容器中的元素类型。具体说明查看在线文档:http://www.cplusplus.com/reference/std/memory/auto_ptr/

在2运行时,A b;相当于在栈中存放了一个A类型的变量b,也是当超出作用域后,系统自动回收内存。

在3运行时,声明了一个A*类型的指针,new了一个A类型的对象。如果不显示delete会造成内存泄露,系统不会自动回收。

分别运行1,2,3的结果如下:

15.构造函数的隐式类型转换

#include<iostream>

using namespace std;

class B
{
    public:
    int i;
    B(int j):i(j){cout<<"int"<<endl;};
    //禁止隐式类型转换:explicit B(int j):i(j){cout<<"int"<<endl;};
    ~B(){cout<<"~ B"<<endl;};
};

B Play(B b)
    {
        return b;
    };

int main()
{
    B temp=Play(5);
    return 1;
}

此处Play(5)也可以操作,尽管Play()的单个形参是类类型B,但是在运行中将5隐式类型转换成了B类型。

如果在B的构造函数前面加上explicit就可以禁止隐式类型转化了。

16.链表反转和冒泡排序的代码

#include<stdio.h>
#include<stdlib.h>

typedef struct student* nodep;
typedef struct student
{
    int data;
    nodep next;
}node;
//新建链表
nodep CreatList()
{
    nodep head,p,s;
    int x,circle=1;
    head=(nodep)malloc(sizeof(node));
    p=head;
    while(circle)
    {
        printf("\nplease input the data:");
        scanf("%d",&x);
        if(x!=0)
        {
            s=(nodep)malloc(sizeof(node));
            s->data=x;
            p->next=s;
            p=s;
        }
        else
        {
            circle=0;
        }
        p->next=NULL;
    }
    return head;
}
//打印链表
void PrintList(nodep head)
{
    nodep p=head;
    p=p->next;
    printf("\nlistprint:");
    while(0!=p)
    {
        printf("%d ",p->data);
        p=p->next;
    }
    printf("NULL");
}
int LengthList(nodep head)
{
    nodep p=head;
    int i=0;
    if(NULL==p || NULL==p->next)
    return 0;
    p=p->next;
    while(NULL!=p)
    {
        ++i;
        p=p->next;
    }
    return i;
}
//从小到大排
nodep SortList(nodep head)
{
    int length=LengthList(head);
    nodep p;
    int temp;
    int i,j;
    for(i=1;i<length;i++)//n个数,只用对n-1个数调用排序算法即可
    {
        p=head->next;
        for(j=1;j<length-i;j++)//每次算法调用的循环数为n-i次
        {
            if(p->data > p->next->data)
            {
                temp=p->data;
                p->data=p->next->data;
                p->next->data=temp;
            }
            p=p->next;
        }
    }
    return head;
}
//将链表反转
nodep ReverseList(nodep head)
{
    nodep temp;
    nodep p=head->next;
    if(NULL==p||NULL==head)return NULL;
    nodep next=p->next;
    p->next=NULL;
    while(NULL!=next)
    {
        temp=next;
        next=next->next;
        temp->next=p;
        p=temp;
    }
    head->next=p;
    return head;
}
int main()
{
    nodep head=CreatList();
    nodep result;
    printf("\n刚开始:");
    PrintList(head);
    result=ReverseList(head);
    printf("\n反转以后:");
    PrintList(head);
    SortList(head);
    printf("\n排序以后:");
    PrintList(head);
    return 1;
}

17.内存的基本知识

在内存中有这几个部分,栈:由编译器自动分配和释放。堆:由程序员分配和释放。全局区和静态区:存放全局变量和静态变量的存储。已经初始化的放一起,未初始化的放一起。文字常量区:常量字符串放这里,程序结束后由系统释放。程序代码区:存放函数体的二进制代码。

存取效率问题:比较char* c="aaaaaa";和char c[]="bbbbbb"

aaaaaa全部存放在文字常量区,bbbbbb全部存放在栈里面。

前者是在运行时被赋值的,后者是在编译时就确定了,但是在以后的存取中,在栈上的数组要比指针所指向的字符串(存在堆上)要快。

18.关于死循环

#include<iostream>

using namespace std;

#define MAX 255

int main()
{
    char p[MAX+1];
    char ch;
    for(ch=0;ch<=MAX;ch++)
    {
        p[ch]=ch;
        cout<<hex<<ch<<" ";
    }
    cout<<ch<<" end~"<<endl;
}

分析:char的范围为-128-127. 当for循环中超过127时,ch变为-128,以后一直小于MAX了。由于unsighed char范围是(0~255)解决办法,将ch类型变为unsighed char且将<=变为=. 因为如果只是改类型的话最后ch等于255,加1以后变为0,也是一直小于MAX的,退出for循环以后单独将p[255]进行赋值。修正后代码如下:

#include<iostream>

using namespace std;

#define MAX 255

int main()
{
    char p[MAX+1];
    unsigned char ch;
    for(ch=0;ch<MAX;ch++)
    {
        p[ch]=ch;
        cout<<hex<<ch<<" ";
    }
    p[ch]=ch;
    cout<<ch<<" end~"<<endl;
}
posted on 2011-04-30 21:05  Null_x1  阅读(1625)  评论(0编辑  收藏  举报