1、const 有什么用途?(请至少说明两种)
答:
(1)可以定义 const 常量
(2)const可以修饰函数的参数、返回值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
2、在C++ 程序中调用被 C编译器编译后的函数,为什么要加 extern “C”?
答:C++语言支持函数重载,C语言不支持函数重载。 函数被C++编译后在库中的名字与C语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”来解决名字匹配问题。
3、请简述以下两个for循环的优缺点(5分)
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
} if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
优点:程序简洁
缺点:多执行了N-1次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。 优点:循环的效率高
缺点:程序不简洁
4、有关内存的思考题
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:程序崩溃。
因为GetMemory并不能传递动态内存,
Test函数中的 str一直都是 NULL。
strcpy(str, "hello world");将使程序崩溃。
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test函数会有什么样的结果?
答:可能是乱码。
因为GetMemory返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test函数会有什么样的结果?
答:
(1)能够输出hello
(2)内存泄漏
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
请问运行Test函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。
因为free(str);之后,str成为野指针,
if(str != NULL)语句不起作用。
5、编写strcpy函数(10分)
已知strcpy函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C++/C的字符串库函数,请编写函数 strcpy
char *strcpy(char* strDest, const char* strSrc)
{
assert ((strDest!=NULL) && (strSrc !=NULL));//2分,assert:能测试一个条件并使程序终止
char *address = strDest;//2分
while( (*strDest++ = * strSrc++) != ‘/0’ )//2分
NULL;
return address;// 2分
}
5.1 strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
答:为了实现链式表达式。 // 2分
例如int length = strlen( strcpy( strDest, "hello world") );
6、编写类String的构造函数、析构函数和赋值函数(25分)
已知类String的原型为:
class String
{
public:
String(const char *str = NULL);//普通构造函数
String(const String &other);//拷贝构造函数
~ String(void);// 析构函数
String & operate =(const String &other);// 赋值函数
private:
char* m_data;// 用于保存字符串
};
请编写String的上述4个函数。
标准答案:
// String的析构函数
String::~String(void) // 3分
{
delete [] m_data;
// 由于m_data是内部数据类型,也可以写成delete m_data;
}
// String的普通构造函数
String:: String(const char *str) // 6分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加NULL 判断则更好
*m_data = ‘/0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加NULL 判断则更好
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(const String &other) // 3分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加NULL 判断则更好
strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other) // 13分
{
// (1) 检查自赋值 // 4分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 // 3分
delete [] m_data;
// ()分配新的内存资源,并复制内容// 3分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加NULL 判断则更好
strcpy(m_data, other.m_data);
// ()返回本对象的引用 // 3分
return *this;
}
7、实现双向链表删除一个节点P,在节点P后插入一个节点,写出这两个函数。
void DeleteNode(DuNode *p)
{
p->prior->next=p->next;
p->next->prior=p->prior;
}
void InsertNode(DuNode *p, DuNode *s)//Node "s" is inserted after "p"
{
s->next=p->next;
p->next->prior=s;
p->next=s;
s->prior=p;
}
1. 以下三条输出语句分别输出什么?
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char* str5 = "abc";
const char* str6 = "abc";
cout << boolalpha << ( str1==str2 ) << endl; // 输出什么?
cout << boolalpha << ( str3==str4 ) << endl; // 输出什么?
cout << boolalpha << ( str5==str6 ) << endl; // 输出什么?
答:分别输出false,false,true。str1和str2都是字符数组,每个都有其自己的存储区,它们的值则是各存储区首地址,不等;str3和 str4同上,只是按const语义,它们所指向的数据区不能修改。str5和str6并非数组而是字符指针,并不分配存储区,其后的“abc”以常量形式存于静态数据区,而它们自己仅是指向该区首地址的指针,相等。
2. 以下代码中的两个sizeof用法有问题吗?
void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母
{
for( size_t i=0; i<sizeof(str)/sizeof(str[0]); ++i )
if( 'a'<=str[i] && str[i]<='z' )
str[i] -= ('a'-'A' );
}
char str[] = "aBcDe";
cout << "str字符长度为: " << sizeof(str)/sizeof(str[0]) << endl;
UpperCase( str );
cout << str << endl;
答:函数内的sizeof有问题。根据语法,sizeof如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。函数外的str 是一个静态定义的数组,因此其大小为6,函数内的str实际只是一个指向字符串的指针,没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4。
3. 非C++内建型别 A 和 B,在哪几种情况下B能隐式转化为A?
答:
a. class B : public A { ……} // B公有继承自A,可以是间接继承的
b. class B { operator A( ); } // B实现了隐式转化为A的转化
c. class A { A( const B& ); } // A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数
d. A& operator= ( const A& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个
4. 以下代码有什么问题?
struct Test
{
Test( int ) {}
Test() {}
void fun() {}
};
void main( void )
{
Test a(1);
a.fun();
Test b();
b.fun();
}
答:变量b定义出错。按默认构造函数定义对象,不需要加括号。
5. 以下代码有什么问题?
cout << (true?1:"1") << endl;
答:三元表达式“?:”问号后面的两个操作数必须为同一类型。
6. 以下代码能够编译通过吗,为什么?
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
答:str2定义出错,size2非编译器期间常量,而数组定义要求长度必须为编译期常量 。
7. 以下反向遍历array数组的方法有什么错误?
vector array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 3 );
for( vector::size_type i=array.size()-1; i>=0; --i ) // 反向遍历array数组
{
cout << array[i] << endl;
}
答:首先数组定义有误,应加上类型参数:vector<int> array。其次vector::size_type被定义为unsigned int,即无符号数,这样做为循环变量的i为0时再减1就会变成最大的整数,导致循环失去控制 。
8. 以下代码中的输出语句输出0吗,为什么?
struct CLS
{
int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{
CLS(0);
}
};
CLS obj;
cout << obj.m_i << endl;
答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。
9. C++中的空类,默认产生哪些类成员函数?
答:
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty& ); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};
10. 以下两条输出语句分别输出什么?
float a = 1.0f;
cout << (int)a << endl;
cout << (int&)a << endl;
cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么?
float b = 0.0f;
cout << (int)b << endl;
cout << (int&)b << endl;
cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么?
答:分别输出false和true。注意转换的应用。(int)a实际上是以浮点数a为参数构造了一个整型数,该整数的值是1, (int&)a则是告诉编译器将a当作整数看(并没有做任何实质上的转换 )。因为1以整数形式存放和以浮点形式存放其内存数据是不一样的,因此两者不等。对b的两种转换意义同上,但是0的整数形式和浮点形式其内存数据是一样的,因此在这种特殊情形下,两者相等(仅仅在数值意义上)。
注意,程序的输出会显示 (int&)a=1065353216,这个值是怎么来的呢?前面已经说了,1以浮点数形式存放在内存中,按ieee754规定,其内容为 0x0000803F(已考虑字节反序)。这也就是a这个变量所占据的内存单元的值。当(int&)a出现时,它相当于告诉它的上下文:“把这块地址当做整数看待!不要管它原来是什么。”这样,内容0x0000803F按整数解释,其值正好就是1065353216(十进制数)。
通过查看汇编代码可以证实“(int)a相当于重新构造了一个值等于a的整型数”之说,而(int&)的作用则仅仅是表达了一个类型信息,意义在于为cout<<及==选择正确的重载版本。
11. 以下代码有什么问题?
typedef vector IntArray;
IntArray array;
array.push_back( 1 );
array.push_back( 2 );
array.push_back( 2 );
array.push_back( 3 );
// 删除array数组中所有的2
for( IntArray::iterator itor=array.begin(); itor!=array.end(); ++itor )
{
if( 2 == *itor ) array.erase( itor );
}
答:同样有缺少类型参数的问题。另外,每次调用“array.erase( itor );”,被删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使itor--, 使之从已经前移的下一个元素起继续遍历。
12. 写一个函数,完成内存之间的拷贝。 [考虑问题是否全面]
答:
void* mymemcpy( void *dest, const void *src, size_t count )
{
char* pdest = static_cast<char*>( dest );
const char* psrc = static_cast<const char*>( src );
if( pdest>psrc && pdest<psrc+cout ) 能考虑到这种情况就行了
{
for( size_t i=count-1; i!=-1; --i )
pdest[i] = psrc[i];
}
else
{
for( size_t i=0; i<count; ++i )
pdest[i] = psrc[i];
}
return dest;
}
1.求下面函数的返回值(微软)
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
2. 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化 。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名 。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元 。不能建立数组的引用 。
3. 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的 。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作 ;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元 ,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
4. 在什么时候需要使用“常引用”?
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
例1
int a ;
const int &ra=a;
ra=1; //错误
a=1; //正确
例2
string foo( );
void bar(string & s);
那么下面的表达式将是非法的:
bar(foo( ));
bar("hello world");
原因在于foo( )和"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。
引用型参数应该在能被定义为const的情况下,尽量定义为const 。
5. 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }
好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!
注意事项:
(1)不能返回局部变量的引用 。这条可以参照Effective C++[1]的Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
(2)不能返回函数内部new分配的内存的引用 。这条可以参照Effective C++[1]的Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak 。
(3)可以返回类成员的引用,但最好是const 。这条原则可以参照Effective C++[1]的Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。
(4)流操作符重载 返回值申明为“引用”的作用:
流操作符<<和>>, 这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象 ,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符 。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。
例3
#i nclude <iostream.h>
int &put(int n);
int vals[10];
int error=-1;
void main()
{
put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;
put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;
cout<<vals[0];
cout<<vals[9];
}
int &put(int n)
{
if (n>=0 && n<=9 ) return vals[n];
else { cout<<"subscript error"; return error; }
}
(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用 ,Effective C++[1]的Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。
6. “引用”与多态的关系?
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
例4
Class A; Class B : Class A{...}; B b; A& ref = b;
7. “引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。
8. 什么时候需要“引用”?
流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
以上 2-8 参考: http://blog.csdn.net/wfwd/archive/2006/05/30/763551.aspx
9. 结构与联合有和区别?
1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
10. 下面关于“联合”的题目的输出?
a)
#i nclude <stdio.h>
union
{
int i;
char x[2];
}a;
void main()
{
a.x[0] = 10;
a.x[1] = 1;
printf("%d",a.i);
}
答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A)
b)
main()
{
union{ /*定义一个联合*/
int i;
struct{ /*在联合中定义一个结构*/
char first;
char second;
}half;
}number;
number.i=0x4241; /*联合成员赋值*/
printf("%c%c/n", number.half.first, mumber.half.second);
number.half.first='a'; /*联合中结构成员赋值*/
number.half.second='b';
printf("%x/n", number.i);
getch();
}
答案: AB (0x41对应'A',是低位;Ox42对应'B',是高位)
6261 (number.i和number.half共用一块地址空间)
11. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc) 其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。
答案:
char *strcpy(char *strDest, const char *strSrc)
{
if ( strDest == NULL || strSrc == NULL)
return NULL ;
if ( strDest == strSrc)
return strDest ;
char *tempptr = strDest ;
while( (*strDest++ = *strSrc++) != ‘/0’)
;
return tempptr ;
}
12. 已知String类定义如下:
class String
{
public:
String(const char *str = NULL); // 通用构造函数
String(const String &another); // 拷贝构造函数
~ String(); // 析构函数
String & operater =(const String &rhs); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
尝试写出类的成员函数实现。
答案:
String::String(const char *str)
{
if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断
{
m_data = new char[1] ;
m_data[0] = '/0' ;
}
else
{
m_data = new char[strlen(str) + 1];
strcpy(m_data,str);
}
}
String::String(const String &another)
{
m_data = new char[strlen(another.m_data) + 1];
strcpy(m_data,other.m_data);
}
String& String::operator =(const String &rhs)
{
if ( this == &rhs)
return *this ;
delete []m_data; //删除原来的数据,新开一块内存
m_data = new char[strlen(rhs.m_data) + 1];
strcpy(m_data,rhs.m_data);
return *this ;
}
String::~String()
{
delete []m_data ;
}
13. .h头文件中的ifndef/define/endif 的作用?
答:防止该头文件被重复引用。
14. #i nclude<file.h> 与 #i nclude "file.h"的区别?
答:前者是从Standard Library 的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
15.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用 。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。 例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo( int x, int y );
该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name ”)。
_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )与void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float。
同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。
未加extern "C"声明时的连接方式
假设在C++中,模块A的头文件如下:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo( int x, int y );
#endif
在模块B中引用该函数:
// 模块B实现文件 moduleB.cpp
#i nclude "moduleA.h"
foo(2,3);
实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!
加extern "C"声明后的编译和连接方式
加extern "C"声明后,模块A的头文件变为:
// 模块A头文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C" int foo( int x, int y );
#endif
在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:
(1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;
(2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo。
如果在模块A中函数声明了foo为extern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。
所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++与C及其它语言的混合编程 。
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:
extern "C"的惯用法
(1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h) 时,需进行下列处理:
extern "C"
{
#i nclude "cExample.h"
}
而在C语言的头文件中,对其外部函数只能指定为extern类型 ,C语言中不支持extern "C"声明 ,在.c文件中包含了extern "C"时会出现编译语法错误。
C++引用C函数例子工程中包含的三个文件的源代码如下:
/* c语言头文件 :cExample.h */
#ifndef C_EXAMPLE_H
#define C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c语言实现文件 :cExample.c */
#i nclude "cExample.h"
int add( int x, int y )
{
return x + y;
}
// c++实现文件,调用add:cppFile.cpp
extern "C"
{
#i nclude "cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。
(2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。
C引用C++函数例子工程中包含的三个文件的源代码如下:
//C++头文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++实现文件 cppExample.cpp
#i nclude "cppExample.h"
int add( int x, int y )
{
return x + y;
}
/* C实现文件 cFile.c
/* 这样会编译出错:#i nclude "cExample.h" */
extern int add( int x, int y );
int main( int argc, char* argv[] )
{
add( 2, 3 );
return 0;
}
15题目的解答请参考 《C++中extern “C”含义深层探索 》 注解:
16. 关联、聚合(Aggregation)以及组合(Composition)的区别?
涉及到UML中的一些概念:关联 是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合 表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:
从实现的角度讲,聚合可以表示为:
class A {...} class B { A* a; .....}
而组合 表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:
实现的形式是:
class A{...} class B{ A a; ...}
参考文章: http://blog.csdn.net/wfwd/archive/2006/05/30/763753.aspx
http://blog.csdn.net/wfwd/archive/2006/05/30/763760.aspx
17.面向对象的三个基本特征,并简单叙述之?
1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)
2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。
3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
18. 重载(overload)和重写(overried,有的书也叫做“覆盖”)的区别?
常考的题目。从定义上来说:
重载 :是指允许存在多个同名函数 ,而这些函数的参数表不同 (或许参数个数不同,或许参数类型不同,或许两者都不同)。
重写 :是指子类重新定义复类虚函数的方法。
从实现原理上来说:
重载 :编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer; 和 function func(p:string):integer;。 那么编译器做过修饰后的函数名称可能是这样的: int_func 、 str_func 。对于这两个函数的调用,在编译器间就已经确定了,是静态 的。也就是说,它们的地址在编译期就绑定了(早绑定), 因此,重载和多态无关 !
重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用 属于子类的该函数,这样的函数调用在编译期间是无法确定的 (调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。
19. 多态的作用?
主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用 。
20. Ado与Ado.net的相同与不同?
除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET 和ADO是两种数据访问方式。ADO.net 提供对XML 的支持
C/C++ 笔试、面试题目大汇总(2)
2007-11-24 12:04
21. New delete 与malloc free 的联系与区别?
答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象 ,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.
22. #define DOUBLE(x) x+x , i = 5*DOUBLE(5); i 是多少?
答案:i 为30。
23. 有哪几种情况只能用intialization list 而不能用assignment?
答案:当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。
24. C++是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
25. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在main 函数之前执行。
26. 描述内存分配方式以及它们的区别?
1) 从静态存储区域分配 。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量 。
2) 在栈上创建 。在执行函数时,函数内局部变量的存储单元都可以在栈上创建 ,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配 ,亦称动态内存分配 。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
27.struct 和 class 的区别
答案:struct 的成员默认是公有的,而类的成员默认是私有的。 struct 和 class 在其他方面是功能相当的。
从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。
28.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk)
答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
29. 在8086 汇编下,逻辑地址和物理地址 是怎样转换的?(Intel)
答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址 。
30. 比较C++中的4种类型转换方式?
请参考: http://blog.csdn.net/wfwd/archive/2006/05/30/763785.aspx ,重点是static_cast, dynamic_cast和reinterpret_cast 的区别和应用。
31.分别写出BOOL,int,float,指针类型的变量a 与“零”的比较语句。
答案:
BOOL : if ( !a ) or if(a)
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL) or if(a == NULL)
32.请说出const与#define 相比,有何优点?
答案: 1) const 常量有数据类型,而宏常量没有数据类型 。编译器可以对前者进行类型安全检查 。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
2) 有些集成化的调试工具可以对const 常量进行调试 ,但是不能对宏常量进行调试。
33.简述数组与指针的区别?
数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。
(1)修改内容上的差别
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量 。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。 注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。
char a[] = "hello world";
char *p = a;
cout<< sizeof(a) << endl; // 12 字节
cout<< sizeof(p) << endl; // 4 字节
计算数组和指针的内存容量
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是100 字节
}
34.类成员函数的重载、覆盖和隐藏区别?
答案:
a.成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
b.覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。
c.“隐藏 ”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
35. There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.
答案:( ( a + b ) + abs( a - b ) ) / 2
36. 如何打印出当前源文件的文件名以及源文件的当前行号?
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。
37. main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?
答案:可以,可以用_onexit 注册一个函 数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4 (void);
void main( void )
{
String str("zhanglin");
_onexit( fn1 );
_onexit( fn2 );
_onexit( fn3 );
_onexit( fn4 );
printf( "This is executed first./n" );
}
int fn1()
{
printf( "next./n" );
return 0;
}
int fn2()
{
printf( "executed " );
return 0;
}
int fn3()
{
printf( "is " );
return 0;
}
int fn4()
{
printf( "This " );
return 0;
}
The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.
38. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的?
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
39.文件中有一组整数,要求排序后输出到另一个文件中
答案:
#i nclude<iostream>
#i nclude<fstream>
using namespace std;
void Order(vector<int>& data) //bubble sort
{
int count = data.size() ;
int tag = false ; // 设置是否需要继续冒泡的标志位
for ( int i = 0 ; i < count ; i++)
{
for ( int j = 0 ; j < count - i - 1 ; j++)
{
if ( data[j] > data[j+1])
{
tag = true ;
int temp = data[j] ;
data[j] = data[j+1] ;
data[j+1] = temp ;
}
}
if ( !tag )
break ;
}
}
void main( void )
{
vector<int>data;
ifstream in("c://data.txt");
if ( !in)
{
cout<<"file error!";
exit(1);
}
int temp;
while (!in.eof())
{
in>>temp;
data.push_back(temp);
}
in.close(); //关闭输入文件流
Order(data);
ofstream out("c://result.txt");
if ( !out)
{
cout<<"file error!";
exit(1);
}
for ( i = 0 ; i < data.size() ; i++)
out<<data[i]<<" ";
out.close(); //关闭输出文件流
}
40. 链表题:一个链表的结点结构
struct Node
{
int data ;
Node *next ;
};
typedef struct Node Node ;
(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)
Node * ReverseList(Node *head) //链表逆序
{
if ( head == NULL || head->next == NULL )
return head;
Node *p1 = head ;
Node *p2 = p1->next ;
Node *p3 = p2->next ;
p1->next = NULL ;
while ( p3 != NULL )
{
p2->next = p1 ;
p1 = p2 ;
p2 = p3 ;
p3 = p3->next ;
}
p2->next = p1 ;
head = p2 ;
return head ;
}
(2)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小相同)
Node * Merge(Node *head1 , Node *head2)
{
if ( head1 == NULL)
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
Node *p1 = NULL;
Node *p2 = NULL;
if ( head1->data < head2->data )
{
head = head1 ;
p1 = head1->next;
p2 = head2 ;
}
else
{
head = head2 ;
p2 = head2->next ;
p1 = head1 ;
}
Node *pcurrent = head ;
while ( p1 != NULL && p2 != NULL)
{
if ( p1->data <= p2->data )
{
pcurrent->next = p1 ;
pcurrent = p1 ;
p1 = p1->next ;
}
else
{
pcurrent->next = p2 ;
pcurrent = p2 ;
p2 = p2->next ;
}
}
if ( p1 != NULL )
pcurrent->next = p1 ;
if ( p2 != NULL )
pcurrent->next = p2 ;
return head ;
}
(3)已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)
答案:
Node * MergeRecursive(Node *head1 , Node *head2)
{
if ( head1 == NULL )
return head2 ;
if ( head2 == NULL)
return head1 ;
Node *head = NULL ;
if ( head1->data < head2->data )
{
head = head1 ;
head->next = MergeRecursive(head1->next,head2);
}
else
{
head = head2 ;
head->next = MergeRecursive(head1,head2->next);
}
return head ;
}
41. 分析一下这段程序的输出 (Autodesk)
class B
{
public:
B()
{
cout<<"default constructor"<<endl;
}
~B()
{
cout<<"destructed"<<endl;
}
B(int i):data(i) //B(int) works as a converter ( int -> instance of B)
{
cout<<"constructed by parameter " << data <<endl;
}
private:
int data;
};
B Play( B b)
{
return b ;
}
(1) results:
int main(int argc, char* argv[]) constructed by parameter 5
{ destructed B(5)形参 析构
B t1 = Play(5); B t2 = Play(t1); destructed t1形参析构
return 0; destructed t2 注意顺序!
} destructed t1
(2) results:
int main(int argc, char* argv[]) constructed by parameter 5
{ destructed B(5)形参 析构
B t1 = Play(5); B t2 = Play(10); constructed by parameter 10
return 0; destructed B(10)形参 析构
} destructed t2 注意顺序!
destructed t1
42. 写一个函数找出一个整数数组中,第二大的数 (microsoft)
答案:
const int MINNUMBER = -32767 ;
int find_sec_max( int data[] , int count)
{
int maxnumber = data[0] ;
int sec_max = MINNUMBER ;
for ( int i = 1 ; i < count ; i++)
{
if ( data[i] > maxnumber )
{
sec_max = maxnumber ;
maxnumber = data[i] ;
}
else
{
if ( data[i] > sec_max )
sec_max = data[i] ;
}
}
return sec_max ;
}
43. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。
KMP算法效率最好,时间复杂度是O(n+m)。
44. 多重继承的内存分配问题:
比如有class A : public class B, public class C {}
那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的实现其细节可能不同。
如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。
可以参考《深入探索C++对象模型》,或者:
http://blog.csdn.net/wfwd/archive/2006/05/30/763797.aspx
45. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)
struct node { char val; node* next;}
bool check(const node* head) {} //return false : 无环;true: 有环
一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):
bool check(const node* head)
{
if(head==NULL) return false;
node *low=head, *fast=head->next;
while(fast!=NULL && fast->next!=NULL)
{
low=low->next;
fast=fast->next->next;
if(low==fast) return true;
}
return false;
1.是不是一个父类写了一个virtual 函数,如果子类覆盖它的函数不加virtual ,也能实现多态?
virtual修饰符会被隐形继承的。
private 也被集成,只事派生类没有访问权限而已
virtual可加可不加
子类的空间里有父类的所有变量(static除外)
同一个函数只存在一个实体(inline除外)
子类覆盖它的函数不加virtual ,也能实现多态。
在子类的空间里,有父类的私有变量。私有变量不能直接访问。
--------------------------------------------------------------------------
2.输入一个字符串,将其逆序后输出。(使用C++,不建议用伪码)
#include <iostream>
using namespace std;
void main()
{
char a[50];memset(a,0,sizeof(a));
int i=0,j;
char t;
cin.getline(a,50,'/n');
for(i=0,j=strlen(a)-1;i<strlen(a)/2;i++,j--)
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
cout<<a<<endl;
}
//第二种
string str;
cin>>str;
str.replace;
cout<<str;
--------------------------------------------------------------------------
3.请简单描述Windows内存管理的方法。
内存管理是操作系统中的重要部分,两三句话恐怕谁也说不清楚吧~~
我先说个大概,希望能够抛砖引玉吧
当程序运行时需要从内存中读出这段程序的代码 。代码的位置必须在物理内存中才能被运行,由于现在的操作系统中有非常多的程序运行着,内存中不能够完全放下,所以引出了虚拟内存 的概念。把哪些不常用的程序片断就放入虚拟内存,当需要用到它的时候在load入主存(物理内存)中。这个就是内存管理所要做的事。内存管理还有另外一件事需要做:计算程序片段在主存中的物理位置,以便CPU调度 。
内存管理有块式管理,页式管理,段式和段页式管理 。现在常用段页式管理
块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程 序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但时易于管理。
页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。
段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上(计算机最耗时间的大家都知道是I/O 吧)。
段页式管理:结合了段式管理和页式管理的优点。把主存分为若干页,每一页又分为若干段。好处就很明显,不用我多说了吧。
各种内存管理都有它自己的方法来计算出程序片断在主存中的物理地址,其实都很相似。
这只是一个大概而已,不足以说明内存管理的皮毛。无论哪一本操作系统书上都有详细的讲解
--------------------------------------------------------------------------
4.
#include "stdafx.h"
#define SQR(X) X*X
int main(int argc, char* argv[])
{
int a = 10;
int k = 2;
int m = 1;
a /= SQR(k+m)/SQR(k+m);
printf("%d/n",a);
return 0;
}
这道题目的结果是什么啊?
define 只是定义而已,在编择时只是简单代换X*X而已,并不经过算术法则的
a /= (k+m)*(k+m)/(k+m)*(k+m);
=>a /= (k+m)*1*(k+m);
=>a = a/9;
=>a = 1;
--------------------------------------------------------------------------
5.
const 符号常量;
(1)const char *p
(2)char const *p
(3)char * const p
说明上面三种描述的区别;
如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
(1)const char *p
一个指向char类型的const对象指针,p不是常量,我们可以修改p的值,使其指向不同的char,但是不能改变它指向非char对象,如:
const char *p;
char c1='a';
char c2='b';
p=&c1;//ok
p=&c2;//ok
*p=c1;//error
(2)char const *p
(3)char * const p
这两个好象是一样的,此时*p可以修改,而p不能修改。
(4)const char * const p
这种是地址及指向对象都不能修改。
--------------------------------------------------------------------------
6.下面是C语言中两种if语句判断方式。请问哪种写法更好?为什么?
int n;
if (n == 10) // 第一种判断方式
if (10 == n) // 第二种判断方式
如果少了个=号,编译时就会报错,减少了出错的可能行,可以检测出是否少了=
--------------------------------------------------------------------------
7.下面的代码有什么问题?
void DoSomeThing(...)
{
char* p;
...
p = malloc(1024); // 分配1K的空间
if (NULL == p)
return;
...
p = realloc(p, 2048); // 空间不够,重新分配到2K
if (NULL == p)
return;
...
}
A:
p = malloc(1024); 应该写成: p = (char *) malloc(1024);
没有释放p的空间,造成内存泄漏。
--------------------------------------------------------------------------
8.下面的代码有什么问题?并请给出正确的写法。
void DoSomeThing(char* p)
{
char str[16];
int n;
assert(NULL != p);
sscanf(p, "%s%d", str, n);
if (0 == strcmp(str, "something"))
{
...
}
}
A:
sscanf(p, "%s%d", str, n); 这句该写成: sscanf(p, "%s%d", str, &n);
--------------------------------------------------------------------------
9.下面代码有什么错误?
Void test1()
{
char string[10];
char *str1="0123456789";
strcpy(string, str1);
}
数组越界
--------------------------------------------------------------------------
10.下面代码有什么问题?
Void test2()
{
char string[10], str1[10];
for(i=0; i<10;i++)
{
str1[i] ='a';
}
strcpy(string, str1);
}
数组越界
--------------------------------------------------------------------------
11.下面代码有什么问题?
Void test3(char* str1)
{
char string[10];
if(strlen(str1)<=10)
{
strcpy(string, str1);
}
}
==数组越界
==strcpy拷贝的结束标志是查找字符串中的/0 因此如果字符串中没有遇到/0的话 会一直复制,直到遇到/0,上面的123都因此产生越界的情况
建议使用 strncpy 和 memcpy
--------------------------------------------------------------------------
12.下面代码有什么问题?
#define MAX_SRM 256
DSN get_SRM_no()
{
static int SRM_no; //是不是这里没赋初值?
int I;
for(I=0;I<MAX_SRM;I++,SRM_no++)
{
SRM_no %= MAX_SRM;
if(MY_SRM.state==IDLE)
{
break;
}
}
if(I>=MAX_SRM)
return (NULL_SRM);
else
return SRM_no;
}
系统会初始化static int变量为0,但该值会一直保存,所谓的不可重入...
--------------------------------------------------------------------------
13.写出运行结果:
{// test1
char str[] = "world"; cout << sizeof(str) << ": ";
char *p = str; cout << sizeof(p) << ": ";
char i = 10; cout << sizeof(i) << ": ";
void *pp = malloc(10); cout << sizeof(p) << endl;
}
6:4:1:4
--------------------------------------------------------------------------
14.写出运行结果:
{// test2
union V {
struct X {
unsigned char s1:2;
unsigned char s2:3;
unsigned char s3:3;
} x;
unsigned char c;
} v;
v.c = 100;
printf("%d", v.x.s3);
}
3
--------------------------------------------------------------------------
15.用C++写个程序,如何判断一个操作系统是16位还是32位的?不能用sizeof()函数
A1:
16位的系统下,
int i = 65536;
cout << i; // 输出0;
int i = 65535;
cout << i; // 输出-1;
32位的系统下,
int i = 65536;
cout << i; // 输出65536;
int i = 65535;
cout << i; // 输出65535;
A2:
int a = ~0;
if( a>65536 )
{
cout<<"32 bit"<<endl;
}
else
{
cout<<"16 bit"<<endl;
}
--------------------------------------------------------------------------
16.C和C++有什么不同?
从机制上:c是面向过程的(但c也可以编写面向对象的程序);c++是面向对象的,提供了类。但是,
c++编写面向对象的程序比c容易
从适用的方向:c适合要求代码体积小的,效率高的场合,如嵌入式;c++适合更上层的,复杂的; llinux核心大部分是c写的,因为它是系统软件,效率要求极高。
从名称上也可以看出,c++比c多了+,说明c++是c的超集;那为什么不叫c+而叫c++呢,是因为c++比
c来说扩充的东西太多了,所以就在c后面放上两个+;于是就成了c++
C语言是结构化编程语言,C++是面向对象编程语言。
C++侧重于对象而不是过程,侧重于类的设计而不是逻辑的设计。
--------------------------------------------------------------------------
17.在不用第三方参数的情况下,交换两个参数的值
#include <stdio.h>
void main()
{
int i=60;
int j=50;
i=i+j;
j=i-j;
i=i-j;
printf("i=%d/n",i);
printf("j=%d/n",j);
}
方法二:
i^=j;
j^=i;
i^=j;
方法三:
// 用加减实现,而且不会溢出
a = a+b-(b=a)
--------------------------------------------------------------------------
18.有关位域的面试题(为什么输出的是一个奇怪的字符)
a.t = 'b';效果相当于 a.t= 'b' & 0xf;
'b' --> 01100010
'b' & 0xf -->>00000010
所以输出Ascii码为2的特殊字符
char t:4;就是4bit的字符变量,同样
unsigned short i:8;就是8bit的无符号短整形变量
--------------------------------------------------------------------------
19.int i=10, j=10, k=3; k*=i+j; k最后的值是?
60
--------------------------------------------------------------------------
20.进程间通信的方式有?
进程间通信的方式有 共享内存, 管道 ,Socket ,消息队列 , DDE等
--------------------------------------------------------------------------
21.
struct A
{
char t:4;
char k:4;
unsigned short i:8;
unsigned long m;
}
sizeof(A)=?(不考虑边界对齐)
7
struct CELL // Declare CELL bit field
{
unsigned character : 8; // 00000000 ????????
unsigned foreground : 3; // 00000??? 00000000
unsigned intensity : 1; // 0000?000 00000000
unsigned background : 3; // 0???0000 00000000
unsigned blink : 1; // ?0000000 00000000
} screen[25][80]; // Array of bit fields
二、位结构
位结构是一种特殊的结构, 在需按位访问一个字节或字的多个位时, 位结构
比按位运算符更加方便。
位结构定义的一般形式为:
struct位结构名{
数据类型 变量名: 整型常数;
数据类型 变量名: 整型常数;
} 位结构变量;
其中: 数据类型必须是int(unsigned或signed)。 整型常数必须是非负的整
数, 范围是0~15, 表示二进制位的个数, 即表示有多少位。
变量名是选择项, 可以不命名, 这样规定是为了排列需要。
例如: 下面定义了一个位结构。
struct{
unsigned incon: 8; /*incon占用低字节的0~7共8位*/
unsigned txcolor: 4;/*txcolor占用高字节的0~3位共4位*/
unsigned bgcolor: 3;/*bgcolor占用高字节的4~6位共3位*/
unsigned blink: 1; /*blink占用高字节的第7位*/
}ch;
位结构成员的访问与结构成员的访问相同。
例如: 访问上例位结构中的bgcolor成员可写成:
ch.bgcolor
注意:
1. 位结构中的成员可以定义为unsigned, 也可定义为signed, 但当成员长
度为1时, 会被认为是unsigned类型。因为单个位不可能具有符号。
2. 位结构中的成员不能使用数组和指针, 但位结构变量可以是数组和指针,
如果是指针, 其成员访问方式同结构指针。
3. 位结构总长度(位数), 是各个位成员定义的位数之和, 可以超过两个字
节。
4. 位结构成员可以与其它结构成员一起使用。
例如:
struct info{
char name[8];
int age;
struct addr address;
float pay;
unsigned state: 1;
unsigned pay: 1;
}workers;
上例的结构定义了关于一个工人的信息。其中有两个位结构成员, 每个位结
构成员只有一位, 因此只占一个字节但保存了两个信息, 该字节中第一位表示工
人的状态, 第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。
--------------------------------------------------------------------------
22.下面的函数实现在一个固定的数上加上一个数,有什么错误,改正
int add_n(int n)
{
static int i=100;
i+=n;
return i;
}
答:
因为static使得i的值会保留上次的值。
去掉static就可了
--------------------------------------------------------------------------
23.下面的代码有什么问题?
class A
{
public:
A() { p=this; }
~A() { if(p!=NULL) { delete p; p=NULL; } }
A* p;
};
答:
会引起无限递归
--------------------------------------------------------------------------
24.
union a {
int a_int1;
double a_double;
int a_int2;
};
typedef struct
{
a a1;
char y;
} b;
class c
{
double c_double;
b b1;
a a2;
};
输出cout<<sizeof(c)<<endl;的结果?
答:
VC6环境下得出的结果是32
另:
我(sun)在VC6.0+win2k下做过试验:
short - 2
int-4
float-4
double-8
指针-4
sizeof(union),以结构里面size最大的为union的size
解析C语言中的sizeof
一、sizeof的概念
sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。
二、sizeof的使用方法
1、用于数据类型
sizeof使用形式:sizeof(type)
数据类型必须用括号括住。如sizeof(int)。
2、用于变量
sizeof使用形式:sizeof(var_name)或sizeof var_name
变量名可以不用括号括住。如sizeof (var_name),sizeof var_name等都是正确形式。带括号的用法更普遍,大多数程序员采用这种形式。
注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。
如sizeof(max)若此时变量max定义为int max(),sizeof(char_v) 若此时char_v定义为char char_v [MAX]且MAX未知,sizeof(void)都不是正确形式。
三、sizeof的结果
sizeof操作符的结果类型是size_t,它在头文件
中typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
1、若操作数具有类型char、unsigned char或signed char,其结果等于1。
ANSI C正式规定字符类型为1字节。
2、int、unsigned int 、short int、unsigned short 、long int 、unsigned long 、 float、double、long double类型的sizeof 在ANSI C中没有具体规定,大小依赖于实现,一般可能分别为2、2、2、2、 4、4、4、8、10。
3、当操作数是指针时,sizeof依赖于编译器。例如Microsoft C/C++7.0中,near类指针字节数为2,far、huge类指针字节数为4。一般Unix的指针字节数为4。
4、当操作数具有数组类型时,其结果是数组的总字节数。
5、联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。
让我们看如下结构:
struct {char b; double x;} a;
在某些机器上sizeof(a)=12,而一般sizeof(char)+ sizeof(double)=9。
这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址。
6、如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。
四、sizeof与其他操作符的关系
sizeof的优先级为2级,比/、%等3级运算符优先级高。它可以与其他操作符一起组成表达式。如i*sizeof(int);其中i为int类型变量。
五、sizeof的主要用途
1、sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:
void *malloc(size_t size),
size_t fread(void * ptr,size_t size,size_t nmemb,FILE * stream)。
2、sizeof的另一个的主要用途是计算数组中元素的个数。例如:
void * memset(void * s,int c,sizeof(s))。
六、建议
由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。
=============================================================
本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。
1、 sizeof应用在结构上的情况
请看下面的结构:
struct MyStruct
{
double dda1;
char dda;
int type
};
对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?
其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了"对齐"处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。
类型
对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
Char
偏移量必须为sizeof(char)即1的倍数
int
偏移量必须为sizeof(int)即4的倍数
float
偏移量必须为sizeof(float)即4的倍数
double
偏移量必须为sizeof(double)即8的倍数
Short
偏移量必须为sizeof(short)即2的倍数
各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。
下面用前面的例子来说明VC到底怎么样来存放结构的。
struct MyStruct
{
double dda1;
char dda;
int type
};
为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是 sizeof(int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用 sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。
下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况:
struct MyStruct
{
char dda;
double dda1;
int type
};
这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)为24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明)
struct MyStruct
{
char dda;//偏移量为0,满足对齐方式,dda占用1个字节;
double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8
//的倍数,需要补足7个字节才能使偏移量变为8(满足对齐
//方式),因此VC自动填充7个字节,dda1存放在偏移量为8
//的地址上,它占用8个字节。
int type;//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍
//数,满足int的对齐方式,所以不需要VC自动填充,type存
//放在偏移量为16的地址上,它占用4个字节。
};//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构
//的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof
//(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为
//sizeof(double)=8的倍数。
所以该结构总的大小为:sizeof(MyStruc)为1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。
VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。
VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;
否则必须为n的倍数。下面举例说明其用法。
#pragma pack(push) //保存对齐状态
#pragma pack(4)//设定为4字节对齐
struct test
{
char m1;
double m4;
int m3;
};
#pragma pack(pop)//恢复对齐状态
以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n),m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析)
2、 sizeof用法总结
在VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。
A. 参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。
B. 参数为数组或指针。下面举例说明.
int a[50]; //sizeof(a)=4*50=200; 求数组所占的空间大小
int *a=new int[50];// sizeof(a)=4; a为一个指针,sizeof(a)是求指针
//的大小,在32位系统中,当然是占4个字节。
C. 参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。
第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一
个实例在内存中都有唯一的地址。
下面举例说明,
Class Test{int a;static double c};//sizeof(Test)=4.
Test *s;//sizeof(s)=4,s为一个指针。
Class test1{ };//sizeof(test1)=1;
D. 参数为其他。下面举例说明。
int func(char s[5]);
{
cout<<sizeof(s);//这里将输出4,本来s为一个数组,但由于做为函
//数的参数在传递的时候系统处理为一个指针,所
//以sizeof(s)实际上为求指针的大小。
return 1;
}
sizeof(func("1234"))=4//因为func的返回类型为int,所以相当于
//求sizeof(int).
以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配策略,这样的话可以避免一些错误。
--------------------------------------------------------------------------
25.i最后等于多少?
int i = 1;
int j = i++;
if((i>j++) && (i++ == j)) i+=j;
答:
i = 5
--------------------------------------------------------------------------
26.
unsigned short array[]={1,2,3,4,5,6,7};
int i = 3;
*(array + i) = ?
答:
4
--------------------------------------------------------------------------
27.
class A
{
virtual void func1();
void func2();
}
Class B: class A
{
void func1(){cout << "fun1 in class B" << endl;}
virtual void func2(){cout << "fun2 in class B" << endl;}
}
A, A中的func1和B中的func2都是虚函数.
B, A中的func1和B中的func2都不是虚函数.
C, A中的func2是虚函数.,B中的func1不是虚函数.
D, A中的func2不是虚函数,B中的func1是虚函数.
答:
A
--------------------------------------------------------------------------
28.
数据库:抽出部门,平均工资,要求按部门的字符串顺序排序,不能含有"human resource"部门,
employee结构如下:employee_id, employee_name, depart_id,depart_name,wage
答:
select depart_name, avg(wage)
from employee
where depart_name <> 'human resource'
group by depart_name
order by depart_name
--------------------------------------------------------------------------
29.
给定如下SQL数据库:Test(num INT(4)) 请用一条SQL语句返回num的最小值,但不许使用统计功能,如MIN,MAX等
答:
select top 1 num
from Test
order by num desc
--------------------------------------------------------------------------
30.
输出下面程序结果。
#include <iostream.h>
class A
{
public:
virtual void print(void)
{
cout<<"A::print()"<<endl;
}
};
class B:public A
{
public:
virtual void print(void)
{
cout<<"B::print()"<<endl;
};
};
class C:public B
{
public:
virtual void print(void)
{
cout<<"C::print()"<<endl;
}
};
void print(A a)
{
a.print();
}
void main(void)
{
A a, *pa,*pb,*pc;
B b;
C c;
pa=&a;
pb=&b;
pc=&c;
a.print();
b.print();
c.print();
pa->print();
pb->print();
pc->print();
print(a);
print(b);
print(c);
}
A:
A::print()
B::print()
C::print()
A::print()
B::print()
C::print()
A::print()
A::print()
A::print()
--------------------------------------------------------------------------
31.
试编写函数判断计算机的字节存储顺序是开序(little endian)还是降序(bigendian)
答:
bool IsBigendian()
{
unsigned short usData = 0x1122;
unsigned char *pucData = (unsigned char*)&usData;
return (*pucData == 0x22);
}
--------------------------------------------------------------------------
32.简述Critical Section和Mutex的不同点
答:
对几种同步对象的总结
1.Critical Section
A.速度快
B.不能用于不同进程
C.不能进行资源统计(每次只可以有一个线程对共享资源进行存取)
2.Mutex
A.速度慢
B.可用于不同进程
C.不能进行资源统计
3.Semaphore
A.速度慢
B.可用于不同进程
C.可进行资源统计(可以让一个或超过一个线程对共享资源进行存取)
4.Event
A.速度慢
B.可用于不同进程
C.可进行资源统计
--------------------------------------------------------------------------
33.一个数据库中有两个表:
一张表为Customer,含字段ID,Name;
一张表为Order,含字段ID,CustomerID(连向Customer中ID的外键),Revenue;
写出求每个Customer的Revenue总和的SQL语句。
建表
create table customer
(
ID int primary key,Name char(10)
)
go
create table [order]
(
ID int primary key,CustomerID int foreign key references customer(id) , Revenue float
)
go
--查询
select Customer.ID, sum( isnull([Order].Revenue,0) )
from customer full join [order]
on( [order].customerid=customer.id )
group by customer.id
--------------------------------------------------------------------------
34.请指出下列程序中的错误并且修改
void GetMemory(char *p){
p=(char *)malloc(100);
}
void Test(void){
char *str=NULL;
GetMemory=(str);
strcpy(str,"hello world");
printf(str);
}
A:错误--参数的值改变后,不会传回
GetMemory并不能传递动态内存,Test函数中的 str一直都是 NULL。
strcpy(str, "hello world");将使程序崩溃。
修改如下:
char *GetMemory(){
char *p=(char *)malloc(100);
return p;
}
void Test(void){
char *str=NULL;
str=GetMemory(){
strcpy(str,"hello world");
printf(str);
}
方法二:void GetMemory2(char **p)变为二级指针.
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
--------------------------------------------------------------------------
35.程序改错
class mml
{
private:
static unsigned int x;
public:
mml(){ x++; }
mml(static unsigned int &) {x++;}
~mml{x--;}
pulic:
virtual mon() {} = 0;
static unsigned int mmc(){return x;}
......
};
class nnl:public mml
{
private:
static unsigned int y;
public:
nnl(){ x++; }
nnl(static unsigned int &) {x++;}
~nnl{x--;}
public:
virtual mon() {};
static unsigned int nnc(){return y;}
......
};
代码片断:
mml* pp = new nnl;
..........
delete pp;
A:
基类的析构函数应该为虚函数
virtual ~mml{x--;}
--------------------------------------------------------------------------
36.101个硬币100真、1假,真假区别在于重量。请用无砝码天平称两次给出真币重还是假币重的结论。
答:
101个先取出2堆,
33,33
第一次称,如果不相等,说明有一堆重或轻
那么把重的那堆拿下来,再放另外35个中的33
如果相等,说明假的重,如果不相等,新放上去的还是重的话,说明假的轻(不可能新放上去的轻)
第一次称,如果相等的话,这66个肯定都是真的,从这66个中取出35个来,与剩下的没称过的35个比
下面就不用说了
方法二:
第3题也可以拿A(50),B(50)比一下,一样的话拿剩下的一个和真的比一下。
如果不一样,就拿其中的一堆。比如A(50)再分成两堆25比一下,一样的话就在
B(50)中,不一样就在A(50)中,结合第一次的结果就知道了。
--------------------------------------------------------------------------
37.static变量和static 函数各有什么特点?
答:
static变量:在程序运行期内一直有效,如果定义在函数外,则在编译单元内可见,如果在函数内,在在定义的block内可见;
static函数:在编译单元内可见;
--------------------------------------------------------------------------
38.用C 写一个输入的整数,倒着输出整数的函数,要求用递归方法 ;
答:
void fun( int a )
{
printf( "%d", a%10 );
a /= 10;
if( a <=0 )return;
fun( a );
}
--------------------------------------------------------------------------
39.写出程序结果:
void Func(char str[100])
{
printf("%d/n", sizeof(str));
}
答:
4
分析:
指针长度
--------------------------------------------------------------------------
40.int id[sizeof(unsigned long)];
这个对吗?为什么??
答:
对
这个 sizeof是编译时运算符,编译时就确定了
可以看成和机器有关的常量。
本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VC中sizeof的主要用法。
1、 sizeof应用在结构上的情况
请看下面的结构:
struct MyStruct
{
double dda1;
char dda;
int type
};
对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求:
sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13
但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。你知道为什么在VC中会得出这样一个结果吗?
其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了"对齐"处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。
类型
对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
Char
偏移量必须为sizeof(char)即1的倍数
int
偏移量必须为sizeof(int)即4的倍数
float
偏移量必须为sizeof(float)即4的倍数
double
偏移量必须为sizeof(double)即8的倍数
Short
偏移量必须为sizeof(short)即2的倍数
各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍?/textarea>
C++面试汇总4
2007年12月29日 星期六 09:00 P.M.
摩托罗拉C++面试题
最近经常在上CSDN 看看,关注里面各位程序员的BLOG,主要是C++方面的,毕竟可以了解一些有用的东西。找到的一二摩托罗拉的C++面试题,学习下。
1.介绍一下STL,详细说明STL如何实现vector。
Answer:
STL (标准模版库,Standard Template Library.它由容器算法迭代器组成。
STL有以下的一些优点:
可以方便容易地实现搜索数据或对数据排序等一系列的算法;
调试程序时更加安全和方便;
即使是人们用STL在UNIX平台下写的代码你也可以很容易地理解(因为STL是跨平台的 )。
vector实质上就是一个动态数组,会根据数据的增加,动态的增加数组空间。
2.如果用VC开发程序,常见这么几个错误,C2001,c2005,c2011,这些错误的原因是什么。
Answer:
在学习VC++的过程中,遇到的LNK2001错误的错误消息主要为:
unresolved external symbol “symbol”(不确定的外部“符号”)。
如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或标签,将产生此错误消息。
一般来说,发生错误的原因有两个:一是所引用的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本的连接库。
编程中经常能遇到LNK2005错误——重复定义错误,其实LNK2005错误并不是一个很难解决的错误.
3.继承和委派有什么分别,在决定使用继承或者委派的时候需要考虑什么。
在OOD,OOP中,组合优于继承.
当然多态的基础是继承,没有继承多态无从谈起。
当对象的类型不影响类中函数的行为时,就要使用模板来生成这样一组类。
当对象的类型影响类中函数的行为时,就要使用继承来得到这样一组类.
4.指针和引用有什么分别;如果传引用比传指针安全,为什么?如果我使用常量指针难道不行吗?
(1) 引用在创建的同时必须初始化,即引用到一个有效的对象;而指针在定义的时候不必初始化,可以在定义后面的任何地方重新赋值.
(2) 不存在NULL引用,引用必须与合法的存储单元关联;而指针则可以是NULL.
(3) 引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用;而指针在任何时候都可以改变为指向另一个对象.给引用赋值并不是改变它和原始对象的绑定关系.
(4) 引用的创建和销毁并不会调用类的拷贝构造函数
(5) 语言层面,引用的用法和对象一样;在二进制层面,引用一般都是通过指针来实现的,只不过编译器帮我们完成了转换.
不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,显得很安全。
const 指针仍然存在空指针,并且有可能产生野指针.
总的来说:引用既具有指针的效率,又具有变量使用的方便性和直观性.
5.参数传递有几种方式;实现多态参数传递采用什么方式,如果没有使用某种方式原因是什么;
传值,传指针或者引用
6.结合一个项目说明你怎样应用设计模式的理念。
设计模式更多考虑是扩展和重用,而这两方面很多情况下,往往会被忽略。
不过,我不建议滥用设计模式,以为它有可能使得简单问题复杂化.
7.介绍一下你对设计模式的理解。(这个过程中有很多很细节的问题随机问的)
设计模式概念是由建筑设计师Christopher Alexander提出:"每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心.这样,你就能一次又一次地使用该方案而不必做重复劳动 ."上述定义是对设计模式的广义定义.将其应用到面向对象软件的领域内,就形成了对设计模式的狭义定义.
可以简单的认为:设计模式就是解决某个特定的面向对象软件问题的特定方法, 并且已经上升到理论程度。
框架与设计模式的区别:
1,设计模式和框架针对的问题域不同.设计模式针对面向对象的问题域;框架针对特定业务的问题域
2,设计模式比框架更为抽象.设计模式在碰到具体问题后,才能产生代码;框架已经可以用代码表示
3,设计模式是比框架更小的体系结构元素.框架中可以包括多个设计模式
设计模式就像武术中基本的招式.将这些招式合理地纵组合起来,就形成套路(框架),框架是一种半成品.
8.C++和C定义结构的分别是什么。
C language 的结构仅仅是数据的结合
C plus plus的struct 和 class 其实具备几乎一样的功能,只是默认的访问属性不一样而已。
9.构造函数可否是虚汗数,为什么?析构函数呢,可否是纯虚的呢?
构造函数不能为虚函数,要构造一个对象,必须清楚地知道要构造什么,否则无法构造一个对象 。
析构函数可以为纯虚函数。
10,拷贝构造函数相关问题,深拷贝,浅拷贝,临时对象等。
深拷贝意味着拷贝了资源和指针,而浅拷贝只是拷贝了指针,没有拷贝资源
这样使得两个指针指向同一份资源,造成对同一份析构两次,程序崩溃。
临时对象的开销比局部对象小些。
11.结合1个你认为比较能体现OOP思想的项目,用UML来描述。(最好这个项目继承,多态,虚函数都有体现)这个问题大概会占面试时间的一半,并且会问很多问题,一不小心可能会被问住)。
。。。
12。基类的有1个虚函数,子类还需要申明为virtual吗?为什么。
不申明没有关系的。
不过,我总是喜欢显式申明,使得代码更加清晰。
13.C也可以通过精心封装某些函数功能实现重用,那C++的类有什么优点吗,难道仅仅是为实现重用。
并不仅仅是这样的。
OOD,OOP从根本上改变了程序设计模式和设计思想,具备重大和深远的意义。
类的三大最基本的特征:封装,继承,多态.
14.C++特点是什么,如何实现多态?画出基类和子类在内存中的相互关系。
多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。
子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数,友元函数等等
15.为什么要引入抽象基类和纯虚函数?
主要目的是为了实现一种接口的效果。
16.介绍一下模板和容器。如何实现?(也许会让你当场举例实现)
模板可以说比较古老了,但是当前的泛型编程实质上就是模板编程。
它体现了一种通用和泛化的思想。
STL有7种主要容器:vector,list,deque,map,multimap,set,multiset.
17.你如何理解MVC。简单举例来说明其应用。
MVC模式是observer 模式的一个特例,典型的有MFC里面的文档视图架构。
18,多重继承如何消除向上继承的二义性。
使用虚拟 继承即可.
2007年Google笔试题
1、 两个二进制数的异或结果
2、 递归函数最终会结束,那么这个函数一定(不定项选择):
1. 使用了局部变量 2. 有一个分支不调用自身
3. 使用了全局变量或者使用了一个或多个参数
3、以下函数的结果?
int cal(int x)
{
if(x==0)
return 0;
else
return x+cal(x-1);
}
4、 以下程序的结果?
void foo(int*a, int* b)
{
*a = *a+*b;
*b = *a-*b;
*a = *a-*b;
}
void main()
{
int a=1, b=2, c=3;
foo(&a,&b);
foo(&b,&c);
foo(&c,&a);
printf("%d, %d, %d", a,b,c);
}
5、下面哪项不是链表优于数组的特点?
1. 方便删除 2. 方便插入 3. 长度可变 4. 存储空间小
6、T(n) = 25T(n/5)+n^2的时间复杂度?
7、n个顶点,m条边的全连通图,至少去掉几条边才能构成一棵树?
8、正则表达式(01|10|1001|0110)*与下列哪个表达式一样?
1.(0|1)* 2.(01|01)* 3.(01|10)* 4.(11|01)* 5.(01|1)*
9、如何减少换页错误?
1. 进程倾向于占用CPU 2. 访问局部性(locality of reference)满足进程要求
3. 进程倾向于占用I/O 4.使用基于最短剩余时间(shortest remaining time)的调度机制
5. 减少页大小
10、实现两个N*N矩阵的乘法,矩阵由一维数组表示
11、找到单向链表中间那个元素,如果有两个则取前面一个
12、长度为n的整数数组,找出其中任意(n-1)个乘积最大的那一组,只能用乘法,不可以用除法。要求对算法的时间复杂度和空间复杂度作出分析,不要求写程序
笔试时间90 分钟。请考生认真答题,切勿轻视。
一、请填写BOOL , float, 指针变量与“零值”比较的 if 语句。(10 分)
提示:这里“零值”可以是0, 0.0 , FALSE 或者“空指针”。例如 int 变量 n 与“零值”
比较的 if 语句为:
if ( n == 0
)
if ( n != 0
)
以此类推。
请写出 BOOL flag 与“零值”比较的 if 语句:
请写出 float x 与“零值”比较的 if 语句:
请写出char *p 与“零值”比较的 if 语句:
二、以下为Windows NT 下的32 位C++ 程序,请计算sizeof 的值(10 分)
char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算
sizeof (str ) =
sizeof ( p ) =
sizeof ( n ) =
void Func ( char str[100])
{
请计算
sizeof( str ) =
}
void *p = malloc( 100 );
请计算
sizeof ( p ) =
三、简答题(25 分)
1、头文件中的 ifndef/define/endif 干什么用?
2、#i nclude <filename.h> 和 #i nclude “filename.h” 有什么区别?
3、const 有什么用途?(请至少说明两种)
4、在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C” 声明?
5、请简述以下两个for 循环的优缺点
// 第一个
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
优点:
缺点:
// 第二个
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
优点:
缺点:
四、有关内存的思考题(20 分)
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test 函数会有什么样的结果?
答:
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test 函数会有什么样的结果?
答:
Void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test 函数会有什么样的结果?
答:
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
请问运行Test 函数会有什么样的结果?
答:
五、编写strcpy 函数(10 分)
已知strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数 strcpy
(2)strcpy 能把strSrc 的内容复制到strDest ,为什么还要char * 类型的返回值?
六、编写类String 的构造函数、析构函数和赋值函数(25 分)
已知类String 的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写String 的上述4 个函数。
附录C :C++/C 试题的答案与评分标准
一、请填写BOOL , float, 指针变量与“零值”比较的 if 语句。(10 分)
请写出 BOOL flag 与“零值”比较的 if 语句。(3 分)
标准答案:
if ( flag )
if ( !flag )
如下写法均属不良风格,不得分。
if (flag == TRUE)
if (flag == 1 )
if (flag == FALSE)
if (flag == 0)
请写出 float x 与“零值”比较的 if 语句。(4 分)
标准答案示例:
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON)
不可将浮点变量用“==” 或“!=” 与数字比较,应该设法转化成“>=”或“<=”此
类形式。
如下是错误的写法,不得分。
if (x == 0.0)
if (x != 0.0)
请写出char *p 与“零值”比较的 if 语句。(3 分)
标准答案:
if (p == NULL)
if (p != NULL)
如下写法均属不良风格,不得分。
if (p == 0)
if (p != 0)
if (p)
if (!)
二、以下为Windows NT 下的32 位C++ 程序,请计算sizeof 的值(10 分)
char str[] = “Hello” ;
char *p = str ;
int n = 10;
请计算
sizeof (str ) = 6 (2 分)
sizeof ( p ) = 4 (2 分)
sizeof ( n ) = 4 (2 分)
void Func ( char str[100])
{
请计算
sizeof( str ) = 4 (2 分)
}
void *p = malloc( 100 );
请计算
sizeof ( p ) = 4 (2 分)
三、简答题(25 分)
1、头文件中的 ifndef/define/endif 干什么用?(5 分)
答:防止该头文件被重复引用。
2、#i nclude <filename.h> 和 #i nclude “filename.h” 有什么区别?(5 分)
答:对于#i nclude <filename.h> ,编译器从标准库路径开始搜索 filename.h
对于#i nclude “filename.h” ,编译器从用户的工作路径开始搜索 filename.h
3、const 有什么用途?(请至少说明两种)(5 分)
答:(1)可以定义 const 常量
(2)const 可以修饰函数的参数、返回值,甚至函数的定义体。被const 修饰的东
西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
4、在C++ 程序中调用被 C 编译器编译后的函数,为什么要加 extern “C”?(5 分)
答:C++ 语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字
与C 语言的不同。假设某个函数的原型为:void foo(int x, int y);
该函数被C 编译器编译后在库中的名字为_foo ,而C++ 编译器则会产生像_foo_int_int 之类的名字。
C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。
5、请简述以下两个for 循环的优缺点(5 分)
for (i=0; i<N; i++)
{
if (condition)
DoSomething();
else
DoOtherthing();
}
优点:程序简洁
缺点:多执行了N-1 次逻辑判断,并且打断了循环“流水线”作业,使得编译器不能对循环进行优化处理,降低了效率。
if (condition)
{
for (i=0; i<N; i++)
DoSomething();
}
else
{
for (i=0; i<N; i++)
DoOtherthing();
}
优点:循环的效率高
缺点:程序不简洁
四、有关内存的思考题(每小题5 分,共20 分)
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
请问运行Test 函数会有什么样的结果?
答:程序崩溃。因为GetMemory 并不能传递动态内存,Test 函数中的 str 一直都是 NULL。strcpy(str, "hello world");将使程序崩溃。
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
请问运行Test 函数会有什么样的结果?
答:可能是乱码。因为GetMemory 返回的是指向“栈内存”的指针,该指针的地址不是 NULL,但其原现的内容已经被清除,新内容不可知。
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
请问运行Test 函数会有什么样的结果?
答:
(1)能够输出hello
(2)内存泄漏
void Test(void)
{
char *str = (char *) malloc(100);
strcpy(str, “hello”);
free(str);
if(str != NULL)
{
strcpy(str, “world”);
printf(str);
}
}
请问运行Test 函数会有什么样的结果?
答:篡改动态内存区的内容,后果难以预料,非常危险。因为free(str);之后,str 成为野指针, if(str != NULL)语句不起作用。
五、编写strcpy 函数(10 分)
已知strcpy 函数的原型是
char *strcpy(char *strDest, const char *strSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数 strcpy
char *strcpy(char *strDest, const char *strSrc);
{
assert((strDest!=NULL) && (strSrc !=NULL)); // 2分
char *address = strDest; // 2 分
while( (*strDest++ = * strSrc++) != ‘/0’ ) // 2 分
NULL ;
return address ; // 2 分
}
(2)strcpy 能把strSrc 的内容复制到strDest ,为什么还要char * 类型的返回值?
答:为了实现链式表达式。// 2 分
例如int length = strlen( strcpy( strDest, “hello world”) );
六、编写类String 的构造函数、析构函数和赋值函数(25 分)
已知类String 的原型为:
class String
{
public:
String(const char *str = NULL); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operate =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
请编写String 的上述4 个函数。
标准答案:
// String 的析构函数
String::~String(void) // 3 分
{
delete [] m_data;
// 由于m_data 是内部数据类型,也可以写成 delete m_data;
}
// String 的普通构造函数
String::String(const char *str) // 6 分
{
if(str==NULL)
{
m_data = new char[1]; // 若能加 NULL 判断则更好
*m_data = ‘/0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, str);
}
}
// 拷贝构造函数
String::String(const String &other) // 3 分
{
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);
}
// 赋值函数
String & String::operate =(const String &other) // 13 分
{
// (1) 检查自赋值 // 4 分
if(this == &other)
return *this;
// (2) 释放原有的内存资源 // 3 分
delete [] m_data;
// (3)分配新的内存资源,并复制内容 // 3 分
int length = strlen(other.m_data);
m_data = new char[length+1]; // 若能加 NULL 判断则更好
strcpy(m_data, other.m_data);