赞
踩
本文参考《C++面试宝典》,增加部分知识点,并修正宝典中的部分错误以及补充部分参考回答。
参考回答:首先,new/delete是C++的关键字,而malloc/free是C语言的库函数,后者使用必须指明申请内存空间的大小,对于类类型的对象,后者不会调用构造函数和析构函数
如下例:
MemTest*mTest1=newMemTest[10];
MemTest*mTest2=newMemTest;
int*pInt1=newint[10];
int*pInt2=newint;
delete[]pInt1; //-1-
delete[]pInt2; //-2-
delete[]mTest1;//-3-
delete[]mTest2;//-4-
在-4-处报错。
这就说明:对于内建简单数据类型,delete和delete[]功能是相同的。对于自定义的复杂数据类型,delete和delete[]不能互用。delete[]删除一个数组,delete删除一个指针简单来说,用new分配的内存用delete删除用new[]分配的内存用delete[]删除delete[]会调用数组元素的析构函数。内部数据类型没有析构函数,所以问题不大。如果你在用delete时没用括号,delete就会认为指向的是单个对象,否则,它就会认为指向的是一个数组。
优点:类继承是在编译时刻静态定义的,且可直接使用,类继承可以较方便地改变父类的实现。
缺点:
封装,继承和多态。
在面向对象程序设计语言中,封装是利用可重用成分构造软件系统的特性,它不仅支持系统的可重用性,而且还有利于提高系统的可扩充性;消息传递可以实现发送一个通用的消息而调用不同的方法;封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。
析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数JAVA无析构函数深拷贝和浅拷贝
从基类继承来的纯虚函数,在派生类中仍是虚函数。如果一个类中至少有一个纯虚函数,那么这个类被称为抽象类(abstractclass)。
抽象类中不仅包括纯虚函数,也可包括虚函数。l抽象类必须用作派生其他类的基类,而不能用于直接创建对象实例。但仍可使用指向抽象类的指针支持运行时多态性。
int func(x)
{
int countx = 0;
while(x)
{
countx ++;
x = x&(x-1);
}
return countx;
}
假定x = 9999。 答案:8
思路:将x转化为2进制,看含有的1的个数。
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式: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( )和"helloworld"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。引用型参数应该在能被定义为const的情况下,尽量定义为const 。
格式:类型标识符 &函数名(形参列表及类型说明){//函数体 }
cout<< "hello" << endl;
#include <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;
}
}
引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。
例4:
Class A;
Class B :Class A
{
...
};
B b;
A& ref = b;
流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
a)
#include <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)
a.x[0] | 10 |
---|---|
a.x[1] | 1 |
char占用一个字节,8位: | |
a.x[0]转化为二进制就为00001010 | |
0 | 0 |
:—: | :—: |
a.x[1]转化为二进制就为00000001 | |
0 | 0 |
:—: | :—: |
最终就是:0000 0001 0000 1010 | |
十六进制为:0x010A | |
十进制为:266 | |
b) |
main()
{
union{ /*定义一个联合*/
int i;
struct
{ /*在联合中定义一个结构*/
char first;
char second;
}half;
}number;
number.i=0x4241; /*联合成员赋值*/
printf("%c%cn",number.half.first, mumber.half.second);
number.half.first='a'; /*联合中结构成员赋值*/
number.half.second='b';
printf("%xn", number.i);
getch();
}
答案: AB (0x41对应’A’,是低位;Ox42对应’B’,是高位)
6261 (number.i和number.half共用一块地址空间)
涉及到UML中的一些概念:关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:从实现的角度讲,聚合可以表示为:
class A
{
...
}
class B
{
A* a;
.....
}
而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:实现的形式是:
class A
{
...
}
class B
{
A a;
...
}
主要是两个:
除了“能够让应用程序处理存储于DBMS中的数据“这一基本相似点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET接口也就完全不同于ADO和OLE DB 接口,这也就是说ADO.NET和ADO是两种数据访问方式。ADO.net 提供对XML的支持。
答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.
答案:i 为30。
答案:当类中含有const、reference成员变量;基类的构造函数都需要初始化表。
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
答案:全局对象的构造函数会在main 函数之前执行。
参考答案:在C++中,可以用struct和class定义类,都可以继承。区别在于:struct的默认继承权限和默认访问权限是public,而class的默认继承权限和默认访问权限是private。
另外,class还可以定义模板类形参,比如template <class T, int i>。
sizeof(A) = 1。编译器不允许一个类的大小为0。那是被编译器插进去的一个char ,使得这个class的不同实体(object)在内存中配置独一无二的地址。 也就是说这个char是用来标识类的不同对象的。肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。
C++中四种类型转换是:static_cast, dynamic_cast, const_cast, reinterpret_cast
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)
Const作用:定义常量、修饰函数参数、修饰函数返回值三个作用。被Const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
数组 | 指针 |
---|---|
保存数据 | 保存数据的地址 |
直接访问数据 | 间接访问数据,首先获得指针的内容,然后将其作为地址,从该地址中提取数据 |
隐式的分配和删除 | 通常用于动态的数据结构 |
通常用于固定数目且数据类型相同的元素 | 通过Malloc分配内存,free释放内存 |
自身即为数据名 | 通常指向匿名数据,操作匿名函数 |
char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof§,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字节
}
There are two int variables:a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find outthe biggest one of the two numbers.
答案:( ( a + b ) + abs( a - b ) ) / 2
答案:
cout << __FILE__ ;
cout<<__LINE__ ;
__FILE__和__LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。
答案:可以,可以用_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 calledwhen the program terminates normally. Successive calls to _onexit create aregister of functions that are executed in LIFO (last-in-first-out) order. Thefunctions passed to _onexit cannot take parameters.
程序正常终止时,将向_onexit函数传递要调用的函数(func)的地址。 连续调用_onexit会创建以LIFO(后进先出)顺序执行的功能的寄存器。 传递给_onexit的函数不能使用参数。
答案:
#ifdef __cplusplus
cout<<"c++";
#else
cout<<"c";
#endif
答案:
#include<iostream>
#include<fstream>
#include <vector>
using namespace std;
void Order(vector<int> &data) //bubble sort
{
int count = data.size();
for (int i = 0; i < count; i++)
{
for (int j = 0; j < count - i - 1; j++)
{
if (data[j] > data[j + 1])
{
int temp = data[j];
data[j] = data[j + 1];
data[j + 1] = temp;
}
}
}
}
void main()
{
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 (int i = 0; i < data.size(); i++)
out << data[i] << " ";
out.close(); //关闭输出文件流
}
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;
}
class B
{
public:
B()
{
cout << "default constructor" << endl;
}
~B()
{
cout << "destructed" << endl;
}
B(int i) :data(i) //B(int) works as aconverter ( int -> instance of B)
{
cout << "constructed by parameter " << data << endl;
}
private:
int data;
};
B Play(B b)
{
return b;
}
问题一:
int main(int argc, char* argv[])
{
B t1 = Play(5);
B t2 = Play(t1);
return 0;
}
//输出
constructed by parameter 5
destructed //B(5)形参析构
destructed //t1形参析构
destructed //t2形参析构
destructed //t1形参析构
问题二:
int main(int argc, char* argv[])
{
B t1 = Play(5);
B t2 = Play(10);
return 0;
}
//输出
constructed by parameter 5 //
destructed //B(5)形参析构
constructed by parameter 10
destructed // B(10)形参析构
destructed //t2形参析构
destructed //t1形参析构
答案:
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;
}
KMP算法效率最好,时间复杂度是O(n+m),
//写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。
/*思路:
*/
#include <iostream>
#include <string>
using namespace std;
int Find(string str_long,string str_short)
{
if(str_long.length()<str_short.length())
{
cout<<"error"<<endl;return 0;
}
string temp;
char str_short_first=str_short[0];
for(int i=0;i<str_long.length();i++)
{
if (str_long[i]==str_short_first)
{
temp.assign(str_long,i,str_short.length());
if(temp==str_short)
return ++i;
else continue;
}
}
}
int main()
{
string str_l;
cout<<"请输入第一个长字符串"<<endl;
cin>>str_l;
string str_s;
cout<<"请输入第一个短字符串"<<endl;
cin>>str_s;
cout<<Find(str_l,str_s)<<endl;
return 0;
}
比如有class A : public class B, public class C {} 那么A的内存结构大致是怎么样的?
这个是compiler-dependent的, 不同的实现其细节可能不同。如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。可以参考《深入探索C++对象模型
思路:
bool check(const node* head)
{
//return false : 无环;true: 有环
//一种O(n)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然)
}
struct node
{
char val;
node* next;
}
bool check(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:
以下是引用片段:
void test1() //数组越界
{
char string[10];
char* str1 = "0123456789";
strcpy(string, str1);
}
试题2:
以下是引用片段:
void test2()
{
char string[10], str1[10];
int i;
for (i = 0; i < 10; i++)
{
str1 = 'a';
}
strcpy(string, str1);
}
试题3:
以下是引用片段:
void test3(char* str1)
{
char string[10];
if (strlen(str1) <= 10)
{
strcpy(string, str1);
}
}
解答:
总分值为10,下面给出几个不同得分的答案::
//2分 以下是引用片段
void strcpy(char *strDest, char *strSrc)
{
while ((*strDest++ = *strSrc++) != ‘\0’);
}
//4分 以下是引用片段:
void strcpy(char *strDest, const char *strSrc)
//将源字符串加const,表明其为输入参数,加2分
{
while ((*strDest++ = *strSrc++) != ‘\0’);
}
//7分 以下是引用片段:
void strcpy(char *strDest, const char *strSrc)
{
//对源地址和目的地址加非0断言,加3分
assert((strDest != NULL) && (strSrc != NULL));
while ((*strDest++ = *strSrc++) != ‘\0’);
}
//10分 以下是引用片段:
//为了实现链式操作,将目的地址返回,加3分!
char * strcpy(char *strDest, const char *strSrc)
{
assert((strDest != NULL) && (strSrc != NULL));
char *address = strDest;
while ((*strDest++ = *strSrc++) != ‘\0’);
return address;
}
对strlen的掌握,它没有包括字符串末尾的’\0’。
读者看了不同分值的strcpy版本,应该也可以写出一个10分的strlen函数了,完美的版本为:
int strlen(const char *str) //输入参数const 以下是引用片段:
{
assert(strt != NULL); //断言字符串地址非0
int len = 0; //注,一定要初始化。
while ((*str++) != '\0')
{
len++;
}
return len;
}
找错题:
试题1:以下是引用片段:
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "helloworld");
printf(str);
}
试题2:以下是引用片段:
char *GetMemory(void)
{
char p[] = "helloworld";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
试题3:以下是引用片段:
void GetMemory(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
试题4:以下是引用片段:
void Test(void)
{
char *str = (char *)malloc(100);
strcpy(str, "hello");
free(str);
... //省略的其它语句
}
解答:
char *str = NULL;
GetMemory( str );
后的str仍然为NULL;
char p[] = "helloworld";
return p;
的p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。
*p = (char *) malloc( num );
后未判断内存是否申请成功,应加上:
if ( *p == NULL )
{
...//进行申请内存失败处理
}
swap(int* p1, int* p2)
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
在swap函数中,p是一个“野”指针,有可能指向系统区,导致程序运行的崩溃。在VC++中DEBUG运行时提示错误“AccessViolation”。该程序应该改为
以下是引用片段:
swap(int* p1, int* p2)
{
int p;
p = *p1;
*p1 = *p2;
*p2 = p;
}
已知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;
}
答:防止该头文件被重复引用。
#include<file.h> 与 #include "file.h"的区别?
答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h。
C++调用C函数需要extern C,因为C语言没有函数重载。
1.What is displayed when f() is called given the code:
class Number {
public:
string type;
Number() : type(“void”) { }
explicit Number(short) : type(“short”) { }
Number(int) : type(“int”) { }
};
void Show(const Number& n) { cout << n.type; }
void f()
{
short s = 42;
Show(s);
}
a) void
b) short
c) int
d) None of the above
double dArray[2] = { 4, 8 }, *p, *q;
p = &dArray[0];
q = p + 1;
cout << q – p << endl;
cout << (int)q - (int)p << endl;
a) 1 and 8
b) 8 and 4
c) 4 and 8
d) 8 and 1
第一个选C;
虽然传入的是short类型,但是short类型的构造函数被生命被explicit,也就是只能显示类型转换,不能使用隐式类型转换。
第二个选A;
第一个是指针加减,按照的是指向地址类型的加减,只跟类型位置有关,q和p指向的数据类型以实际数据类型来算差一个位置,因此是1。而第二个加减是实际指针值得加减,在内存中一个double类型占据8个字节,因此是8
1.完成下列程序
*
*.*.
*…*…*…
*…*…*…*…
*…*…*…*…*…
*…*…*…*…*…*…
*…*…*…*…*…*…*…
*…*…*…*…*…*…*…*…
#include <stdio.h>
#define N 8
int main()
{
int i;
int j;
int k;
-------------------------------------------------------- -
| |
| |
| |
-------------------------------------------------------- -
return 0;
}
答案:
#include<iostream>
using namespace std;
void dian(int n)
{
if (n < 2)
{
while (n--)
{
cout << ".";
}
}
else
cout << "...";
}
void xing()
{
cout << "*";
}
int main()
{
cout << "输入你需要输出的行数:" << endl;
int line;
cin >> line;
for (int i = 1; i <= line; i++)
{
int j = i;
while (j--)
{
xing();
dian(i - 1);
}
cout << endl;
}
return 0;
}
2.完成程序,实现对数组的降序排序
#include<stdio.h>
void sort(int *arr, int len);
int main()
{
int array[] = { 45,56,76,234,1,34,23,2,3 };//数字任//意给出
sort(array, (sizeof(array) / sizeof(array[0])) - 1);
return 0;
}
void sort()
{
____________________________________
| |
| |
----------------------------------------------------
}
答案:
#include <stdio.h>
void sort(int *arr, int len);
int main(void) {
int array[10] = {45,56,76,234,1,34,23,2,3};
int i;
sort(array, (sizeof(array) / sizeof(array[0])) - 1);
for (i = 0; i < (sizeof(array) / sizeof(array[0])) - 1; i++)
{
printf("%d \n", *(array + i));
}
return 0;
}
void sort(int *arr, int len)
{
int i;
int j;
int tmp;
for (i = 0; i < len; i++)
{
for (j = i + 1; j < len; j++)
{
if (*(arr + i) < *(arr + j))
{
tmp = *(arr + i);
*(arr + i) = *(arr + j);
*(arr + j) = tmp;
}
}
}
}
3.费波那其数列,1,1,2,3,5……编写程序求第十项。可以用递归,也可以用其
他方法,但要说明你选择的理由。
#include <stdio.h>
int Pheponatch(int);
int main()
{
printf("The 10th is%d", Pheponatch(10));
return 0;
}
int Pheponatch(int N)
{
--------------------------------
| |
| |
--------------------------------
}
答案:
#include<stdio.h>
int Pheponatch(int);
int main()
{
printf("The 10th is%d", Pheponatch(10));
return 0;
}
int Pheponatch(int n)
{
int n1=1;
int n2=1;
int result=0;
if(n==1||n==2)result=1;
else
{
for(int i=0;i<n-2;i++)
{
int temp=n2;
n2=n1+n2;
n1=temp;
}
result=n2;
}
return result;
}
4.下列程序运行时会崩溃,请找出错误并改正,并且说明原因。
#include
#include
typedef struct
{
TNode* left;
TNode* right;
int value;
} TNode;
TNode* root = NULL;
void append(int N);
int main()
{
append(63);
append(45);
append(32);
append(77);
append(96);
append(21);
append(17); // Again, 数字任意给出
}
void append(int N)
{
TNode* NewNode = (TNode*)malloc(sizeof(TNode));
NewNode->value = N;
if (root == NULL)
{
root = NewNode;
return;
}
else
{
TNode* temp;
temp = root;
while ((N >= temp.value&& temp.left != NULL) || (N != NULL))
{
while (N >= temp.value&& temp.left != NULL)
temp = temp.left;
while (N<temp = temp.right;
}
if (N >= temp.value)
temp.left = NewNode;
else
temp.right = NewNode;
return;
}
}
答案:
//下列程序运行时会崩溃,请找出错误并改正,并且说明原因。
/*
此程序的功能是完成一个有序二叉树的建立,使得左子树结点的值小于根结点,
右子树大于根结点. 题目程序中结构体定义的地方有误,在TNode名字出现之前,
就在结构体内使用TNode,将导致编译错误.另外题目中append函数中的所有temp的点号操作符都应改成->,
因为temp是个指针.至于题目中所说的程序在运行时崩溃出现在append函数中的对temp->left和temp->right操作时候,
因为每创建一个新结点的时候都没将left和right成员初始化为空指针,会导致append函数中的while循环访问到非法内存,
从而导致程序崩溃. 为了方便测试,添加了函数print_tree打印树的结点内容,
另外补充了一个函数free_tree来释放动态分配的内存.修改后的程序如下:
*/
#include <iostream>
using namespace std;
//typedef struct{ //错误1 TNode名字出现前,就在结构内使用TNode将导致编译错误
typedef struct TNode
{ //应该这样写
TNode* left;
TNode* right;
int value;
}TNode;
TNode* root=NULL;
void append(int N);
void free_tree(TNode *root);
void print_tree(TNode *root);
int main()
{
append(63);
append(45);
append(32);
append(77);
append(96);
append(21);
append(17); // Again, 数字任意给出
print_tree(root);
cout<<"\n memory released !\n";
free_tree(root);
system("pause");
return 0;
}
void append(int N)
{
TNode* NewNode=(TNode *)malloc(sizeof(TNode));
if (!NewNode)
{
cout<<"memory alloc failure!\n";
exit(1);
}
NewNode->value=N;
//NewNode->left;
//NewNode->right; //错误2 没有初始化新结点的left和right成员为空指针,
//导致append函数中while循环访问到非法内存,从而导致运行时崩溃
NewNode->left=NULL;
NewNode->right=NULL;
if(root==NULL)
{
root=NewNode;
return;
}
else
{
TNode* temp;
temp=root;
//while((N>=temp.value && temp.left!=NULL) || (N<temp. value && temp. right!=NULL))
//错误3 temp是指针,则下面引用成员应该使用->而不是.(点)
while((N>=temp->value && temp->left!=NULL) || (N<temp->value&&temp->right!=NULL))
{
while(N>=temp->value && temp->left!=NULL)
temp=temp->left;
while(N<temp->value && temp->right!=NULL)
temp=temp->right;
}
if(N>=temp->value)
temp->left=NewNode;
else
temp->right=NewNode;
return;
}
}
//释放内存函数
void free_tree(TNode *root){
if (!root->left && !root->right)
{
free(root);
return;
}
if (root->left)
{
free_tree(root->left);
}
else{
free_tree(root->right);
}
}
//打印函数
void print_tree(TNode *root){
if (!root->left && !root->right)
{
cout<<root->value<<" ";
return;
}
if (root->right)
print_tree(root->right);
cout<<root->value<<" ";
if (root->left)
{
print_tree(root->left);
}
}
OSI七层模型及其包含的协议如下:
参考答案:IP处在互连网络层。负责提供基本的数据封包传送功能,让每一块数据包都能够到达目的主机(但不检查是否被正确接收)。
TCP与UDP在传输层。它提供了节点间的数据传送,应用程序之间的通信服务,主要功能是数据格式化、数据确认和丢失重传等。如传输控制协议(TCP)、用户数据报包议(UDP)等,TCP和UDP给数据包加入传输数据并把它传输到下一层中,这一层负责传送数据,并且确定数据已被送达并接收。
交换机属于OSI第二层即数据链路层设备。它根据MAC地址寻址,通过站表选择路由,站表的建立和维护由交换机自动进行。路由器属于OAI第三层即网络层设备,它根据IP地址进行寻址,通过路由表路由协议产生。交换机最大的好处是快速,路由器最大的好处是控制能力强。
全局变量在数据段,而局部变量在栈,局部 变量在函数结束时内存空间就被系统收回,所以要返回的数组或字符串不要用局部变量定义.extren和在main()函数外定义的变量都称为全局变量,操 作系统和编译器从定义变量为变量分配内存时,从变量的定义和存储区域来分别局部变量和全局变量
8086微处理器共有4个16位的段寄存器,在寻址内存单元时,用它们直接或间接地存放段地址。
代码段寄存器CS:存放当前执行的程序的段地址。
数据段寄存器DS:存放当前执行的程序所用操作数的段地址。
堆栈段寄存器SS:存放当前执行的程序所用堆栈的段地址。
附加段寄存器ES:存放当前执行程序中一个辅助数据段的段地址。
由cs:ip构成指令地址,ss:sp构成堆栈的栈顶地址指针。DS和ES用作数据段和附加段的段地址(段起始地址或段值)
8086/8088微处理器的存储器管理
联想笔试题
1.设计函数 int atoi(char *s)。
#include<iostream>
int atoi(char *s)
{
int num = 0, f = 1, i = 0;
if (s[0] == '-')
{
f = -1;
i = 1;
}
if (s[0] == '+')
{
f = 1;
i = 1;
}
while (s[i] != '\0')
{
if(s[i]<'0' || s[i]>'9')
break;
if (s[i] == '.')
break;
else
{
num = num * 10 + s[i] - '0';
i++;
}
}
return num * f;
}
void main()
{
char ch[] = "+1313.377";
cout << atoi(ch) << endl;
}
2.int i=(j=4,k=8,l=16,m=32); printf(“%d”, i); 输出是多少?
首先
int i=(j=4);
等同于:
int j = 4;
int i = j;
而int i=(j=4,k=8,l=16,m=32);
则等同于:
int j=4, k=8, l=16, m=32;
int i = j;
int i = k;
int i = l;
int i = m;
z最后i = m =32,故输出为32
局部变量
在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外时不能使用这些变量的,它们称为局部变量;
说明:
全局变量
在函数外定义的变量是外部变量,外部变量是全局变量,全局变量可以为本文件中其它函数所共用,它的有效范围从定义变量的位置开始到本源文件结束;
说明:
静态变量
在程序运行期间分配固定的存储空间的变量,叫做静态变量
内联函数和宏很类似,而区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。
当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。这样,内联函数就和普通函数执行效率一样了。
内联函数是不能为虚函数的,但样子上写成了内联的,即隐含的内联方式。在某种情况下,虽然有些函数我们声明为了所谓“内联”方式,但有时系统也会把它当作普通的函数来处理,这里的虚函数也一样,虽然同样被声明为了所谓“内联”方式,但系统会把它当然非内联的方式来处理。
普天C++笔试题
1.实现双向链表删除一个节点P,在节点P后插入一个节点,写出这两个函数。
#include <stdio.h>
#include <stdlib.h>
#define N 10
typedef struct node
{
int data;
struct node *next;
struct node *front;
}ElemSN;
ElemSN *createLink(int *num);
ElemSN *DeleteNode(ElemSN *head, ElemSN *s);
ElemSN *insertNode(ElemSN *head, ElemSN *s, ElemSN *pa);
void outPut(ElemSN *head);
void main()
{
int num[10] = { 1,2,3,4,5,6,7,8,9,10 };
ElemSN *s, *h, *head, *q, *pa;
s = (ElemSN*)malloc(sizeof(ElemSN));//注意要先分配空间才能用啊
s->data = 5;
s->front = NULL;
s->next = NULL;
pa = (ElemSN*)malloc(sizeof(ElemSN));
pa->data = 100;
pa->front = NULL;
pa->next = NULL;
h = createLink(num);
printf("链表建立完如下:\n");
outPut(h);
printf("删除掉元素5以后链表如下:\n");
head = DeleteNode(h, s);
outPut(head);
printf("插入元素后链表如下:\n");
h = createLink(num);
q = insertNode(h, s, pa);
outPut(q);
}
ElemSN *createLink(int *num)
{
int i;
ElemSN *h, *p, *q;
//建立头结点
q = h = (ElemSN*)malloc(sizeof(ElemSN));
h->data = num[0];
h->next = h->front = NULL;
//依次建立其余节点
for (i = 1; i < N; i++)
{
p = (ElemSN*)malloc(sizeof(ElemSN));
p->data = num[i];
p->next = NULL;
p->front = q;
q->next = p;
q = p;
}
return h;
}
ElemSN *DeleteNode(ElemSN *head, ElemSN *s)
{
ElemSN *p, *h, *q;
p = h = head;
q = NULL;
for (; p->data != s->data; q = p, p = p->next);
if (p == head)
{
h = p->next;
h->front = NULL;
p->next = NULL;
free(p);
}
else
{
q->next = p->next;
p->next->front = q;
p->next = p->front = NULL;
free(p);
}
return h;
}
ElemSN *insertNode(ElemSN *head, ElemSN *s, ElemSN *pa)
{
ElemSN *h, *p;
h = head;
for (p = head; (p != NULL) && (p->data != s->data); p = p->next);
//插入分为头结点和其他节点
if (p == NULL)
{
return NULL;
}
if (p == head)
{
pa->next = h;
h->front = pa;
}
else
{
pa->next = p->next;
p->next->front = pa;
pa->front = p;
p->next = pa;
}
return h;
}
void outPut(ElemSN *head)
{
ElemSN *p = head;
while (p != NULL)
{
printf("%d-->", *p);
p = p->next;
}
printf("\n");
}
2.写一个函数,将其中的\t都转换成4个空格。
char*convert(char*strDest, const char*strSrc, int length)
{
char*p = strDest;
int i = 0;
while (*strSrc && i < length)
{
if (*strSrc == '\t')
{
for (int j = 0; j < 4; j++)
*p++ = ' ';
}
else
*p++ = *strSrc;
strSrc++;
i++;
}
*p = '\0';//这里的结束符必须加上,否则可能会出现乱码情况
return strDest;
}
int main()
{
const char *str1 = "abc\tde\tfg";
int len = strlen(str1);
char*str2 = new char;//这个地方需要注意的,必须动态分配,要不会指向空指针!!不能这样定义的char *str2;
//就在这个地方我开始找了很长时间的错,动态分配内存真的很重要!!!
str2 = convert(str2, str1, len);
cout << str2 << endl;
system("pause");
return 0;
}
运行效果图:
Windows程序的入口是WinMain()函数。
Windows应用程序消息处理机制:
不是
对于C++程序而言:
class A();
A a;
int main()
{
//main func
}
程序在执行时,因为会首先初始化全局变量,当这个变量是一个对象时,则会首先调用该对象的构造函数,所以上例中,a的构造函数先执行,然后再执行main()函数,C++中并非所有的动作都是main()函数引起的
引申:怎样在main()函数退出以后再执行一段代码?
全局变量。当程序退出时,全局变量必须销毁,自然会调用全局对象的析构函数,所以剩下的就同构造函数一样了
回调函数定义
就是被调用者回头调用的函数,它是通过函数指针调用的函数。如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所所指向的函数时,此时,就可以称它为回调函数。
进一步解释
回调函数不是由该函数的实现方直接调用的,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
使用回调函数实际上就是在调用某个函数(通常是API函数)时,将自己的一个函数(这个函数为回调函数)的地址作为参数传递给那个被调用的函数。而被调用函数在需要的时候,利用传递的地址调用回调函数。
回调函数由程序员自己调用,当需要调用另一个函数时,这个函数的其中一个参数就是这个回调函数名。系统在必要的时候会调用程序员写的回调函数,这样就可以在回调函数里完成要做的事了。
回调函数实现
(1)声明
(2)定义
(3)设置触发条件,就是在函数中把回调函数名作为一个参数,以便系统调用。
举例
typedef void(*FunPtr)(void);
//定义回调函数
class A
{
public:
//回调函数,必须是声明为static
static void callBackFun(void)
{
...
}
};
//设置触发条件
void Funtype(FunPtr p)
{
p();
}
void main(void)
{
Functype(A::callBackFun);
}
回调函数和API对比
1、栈由编译器自动分配释放空间;堆 一般由程序员分配释放。
2、栈使用的是一级缓存, 它们通常都是被调用时处于存储空间中,调用完毕立即释放;堆则是存放在二级缓存中,生命周期由虚拟机的垃圾回收算法来决定。”
extern “C” const void f(void);
int b;
A const int* a = &b;
B const* int a = &b;
C const int* const a = &b;
D int const* const a = &b;
int const *a 和 const int *a 意义相同,作用等价 同理,本题C、D意义相同。
const int *a 这里const 修饰的是int,而int定义的是一个整值
int *const a 这里const修饰的是 a ,a代表的是一个指针地址 因此不能赋给a其他的地址值,但可以修改a指向的值。
const int * const a 这个代表a所指向的对象的值以及它的地址本身都不能被改变
所以:
第一个const int a, b(即a)的值不能改变,a的值可以改变;
第二个是语法错误;
第三个第四个a、b的值都不能改变;
另外,int * const a,a的值不能改变,*a的值可以改变。
最后得出答案:C和D相同
A、不做检查,和宏一样
B、做类型检查
C、和编译器相关
先说宏和函数的区别:
内联函数与宏的区别:
1.内联函数在运行时可调试,而宏定义不可以;
2.编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会;
3.内联函数可以访问类的成员变量,宏定义则不能;
4.在类中声明同时定义的成员函数,自动转化为内联函数。
答案:B
大唐电信
DTT笔试题
考试时间一小时,第一部分是填空和选择:
1.数列6,10,18,32,“?”,问“?”是几?
2.某人出70买进一个x,80卖出,90买回,100卖出,这桩买卖怎么样?
3.月球绕地球一圈,至少要多少时间?
4.7个人用7小时挖了7米的沟,以同样的速度在50小时挖50米的沟要多少人?
5.鱼头长9,鱼尾等于鱼头加半个鱼身,鱼身等于鱼头加鱼尾,问鱼全长多少?
6.一个小姐买了一块手表,回家发现手表比她家的表慢了两分钟,晚上看新闻的时候又发现她家的表比新闻里的时间慢了两分钟,则 。
A 手表和新闻里的时间一样
B 手表比新闻里的时间慢
C 手表比新闻里的时间快
7.王先生看到一则招聘启事,发现两个公司除了以下条件不同外,其他条件都相同
A 半年年薪50万,每半年涨5万
B 一年年薪100万,每一年涨20万
王先生想去一家待遇比较优厚的公司,他会去哪家?
10.问哪个袋子里有金子?
A袋子上的标签是这样写的:B袋子上的话是对的,金子在A袋子。
B袋子上的标签是这样写的:A袋子上的话是错的,金子在A袋子里。
11.3个人住酒店30块钱,经理找回5块钱,服务生从中藏了2块钱,找给每人1块钱,
3×(101)+2=29,问这是怎么回事?
12.三篇写作,均为书信形式。
(1)一片中文的祝贺信,祝贺某男当了某公司xx
(2)两篇英文的,一是说有事不能应邀,派别人去;另一篇是讨债的,7天不给钱就
走人(主要考business letter格式)。
大唐面试试题
1.什么是中断?中断发生时CPU做什么工作?
2.CPU在上电后,进入操作系统的main()之前必须做什么工作?
3.简述ISO OSI的物理层Layer1,链路层Layer2,网络层Layer3的任务。
4.有线电话和无线电话有何区别?无线电话特别需要注意的是什么?
5.软件开发五个主要step是什么?
6.你在开发软件的时候,这5个step分别占用的时间百分比是多少?
7.makefile文件的作用是什么?
8.UNIX显示文件夹中,文件名的命令是什么?能使文件内容显示在屏幕的命令是什么?
9.(选做)手机用户在从一个基站漫游到另一个基站的过程中,都会发生什么?
网通笔试题
选择题(每题5分,只有一个正确答案)
1.中国1号信令协议属于 的协议。
A ccs B cas C ip D atm
2.isdnpri协议全称是 。
A 综合业务模拟网基速协议
B 综合业务模拟网模拟协议
C 综合业务数字网基率协议
D 综合业务数字网基次协议
3.路由协议中, 协议是用距离作为向量的。
A ospf B bgp C is-is D rip
4.中国智能网中,ssp与scp间最上层的ss7协议是 。
A incs B is41b C is41c D inap
5.dtmf全称是 。
A 双音多频 B多音双频C多音三频 D三音多频
6.计算机的基本组成部分中,不包含下面设备的是 。
A cpu B输入设备 C存储器D接口
7.脉冲编码调制的简称是 。
A pcm B pam C (delta)M D atm
8.普通电话线接口专业称呼是 。
A rj11 B rj45 C rs232 D bnc
9.现有的公共数据网都采用 。
A电路交换技术 B报文交换技术
C语音插空 D分组交换
10.ss7协议中的制止市忙消息简写为 。
A stb B slb C sub D spb
简答题(每题10分)
1.简述普通电话与IP电话的区别。
2.简述随路信令与公路信令的根本区别。
3.说明掩码的主要作用。
4.ss7协议中,有三大要素决定其具体定位,哪三大要素?
5.描述ss7的基本通话过程。
6.简述通信网的组成结构。
7.面向连接与面向非连接各有何利弊?
8.写出爱尔兰的基本计算公式。
9.数据网主要有哪些设备?
10.中国一号协议是如何在被叫号码中插入主叫号码的?
东信笔试题目
笔试:30分钟。
1.压控振荡器的英文缩写。
2.动态随机存储器的英文缩写。
3.选择电阻时要考虑什么?
4.单片机上电后没有运转,首先要检查什么?
5.计算机的基本组成部分及其各自的作用。
6.怎样用D触发器、与或非门组成二分频电路?
答案一
- 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
- 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。
- 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用
答案二:
在C语言中,static主要定义全局静态变量,定义局部静态变量,定义静态函数
一、 定义全局静态变量 :在全局变量前面加上关键字static,该全局变量变成了全局静态变量。全局静态变量有以下特点:
(1) 在全局数据区内分配内存
(2) 如果没有初始化,其默认值为0
(3) 该变量在本文件内从定义开始到文件结束可见
二、 定义局部静态变量:在局部静态变量前面加上关键字static,该局部变量便成了静态局部变量。静态局部变量有以下特点:
(1) 该变量在全局数据区分配内存
(2) 如果不显示初始化,那么将被隐式初始化为0
(3) 它始终驻留在全局数据区,直到程序运行结束
(4) 其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束。
三、 定义静态函数:在函数的返回类型加上static关键字,函数即被定义成静态函数。静态函数有以下特点:
(1) 静态函数只能在本源文件中使用
(2) 在文件作用域中声明的inline函数默认为static
说明:静态函数只是一个普通的全局函数,只不过受static限制,他只能在文件坐在的编译单位内使用,不能呢个在其他编译单位内使用。
在C++语言中新增了两种作用:定义静态数据成员或静态函数成员
(1) 定义静态数据成员。静态数据成员有如下特点:
(1) 内存分配:在程序的全局数据区分配
(2) 初始化和定义:静态数据成员定义时要分配空间,所以不能在类声明中定义
(3) 静态成员函数。静态成员函数与类相联系,不与类的对象相联系。静态成员函数不能访问非静态数据成员。原因很简单,非静态数据成员属于特定的类实例,主要用于对静态数据成员的操作。
(4) 静态成员函数和静态数据成员都没有this指针。
答案三:
1、全局静态变量
在全局变量前加上关键字static,全局变量就定义成一个全局静态变量。
静态存储区,在整个程序运行期间一直存在。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。
作用域:全局静态变量在声明他的文件之外是不可见的,准确地说是从定义之处开始,到文件结尾。
2、局部静态变量
在局部变量之前加上关键字static,局部变量就成为一个局部静态变量。
内存中的位置:静态存储区。
初始化:未经初始化的全局静态变量会被自动初始化为0(自动对象的值是任意的,除非他被显式初始化)。
作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,并没有销毁,而是仍然驻留在内存当中,只不过我们不能再对它进行访问,直到该函数再次被调用,并且值不变。
3.、静态函数
在函数返回类型前加static,函数就定义为静态函数。函数的定义和声明在默认情况下都是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。
函数的实现使用static修饰,那么这个函数只可在本cpp内使用,不会同其他cpp中的同名函数引起冲突。
warning:不要再头文件中声明static的全局函数,不要在cpp内声明非static的全局函数,如果你要在多个cpp中复用该函数,就把它的声明提到头文件里去,否则cpp内部声明需加上static修饰。
4.、类的静态成员
在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。对多个对象来说,静态数据成员只存储一处,供所有对象共用。
5.、类的静态函数
静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。因此,对静态成员的引用不需要用对象名。
在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员(这点非常重要)。如果静态成员函数中要引用非静态成员时,可通过对象来引用。从中可看出,调用静态成员函数使用如下格式:<类名>::<静态成员函数名>(<参数表>);
特定时间内完成特定的任务,实时性与可靠性。
所谓“实时操作系统”,实际上是指操作系统工作时,其各种资源可以根据需要随时进行动态分配。由于各种资源可以进行动态分配,因此其处理事务的能力较强、速度较快。
应该说,实时操作系统是在早期的操作系统基础上发展起来的,早期的操作系统的各种资源都是事先已经分配好的,工作期间这些资源不能再重新进行分配。因此其处理事务的能力较差、速度较慢,现在则称之为“非实时操作系统”。但“非实时操作系统”诞生时,其功能、性能等在当时也是非常强的,人们在未认识到更好的操作系统之前并不将其这样称呼。将来如果新的、功能更强的、实时性能更高的操作系统出现,也许现在称之为“实时”的操作系统则可能将让位于新的“实时操作系统”了。从这方面讲“实时操作系统”是一个相对的概念的。
答 、左右子树都是平衡二叉树 且左右子树的深度差值的绝对值不大于1。
答 、1.没有回收垃圾资源
2.层次太深的递归调用
什么样的函数不能声明为虚函数?
1)不能被继承的函数。
2)不能被重写的函数。
1.普通函数(不能被覆盖)
2.友元函数(C++不支持友元函数继承)
3.内联函数(编译期间展开,虚函数是在运行期间绑定)
4.构造函数(没有对象不能使用构造函数,先有构造函数后有虚函数,虚函数是对对象的动作)
5.静态成员函数(只有一份大家共享)
1)普通函数
普通函数不属于成员函数,是不能被继承的。普通函数只能被重载,不能被重写,因此声明为虚函数没有意义。因为编译器会在编译时绑定函数。
而多态体现在运行时绑定。通常通过基类指针指向子类对象实现多态。
2)友元函数
友元函数不属于类的成员函数,不能被继承。对于没有继承特性的函数没有虚函数的说法。
3)构造函数
首先说下什么是构造函数,构造函数是用来初始化对象的。假如子类可以继承基类构造函数,那么子类对象的构造将使用基类的构造函数,而基类构造函数并不知道子类的有什么成员,显然是不符合语义的。从另外一个角度来讲,多态是通过基类指针指向子类对象来实现多态的,在对象构造之前并没有对象产生,因此无法使用多态特性,这是矛盾的。因此构造函数不允许继承。
4)内联成员函数
我们需要知道内联函数就是为了在代码中直接展开,减少函数调用花费的代价。也就是说内联函数是在编译时展开的。而虚函数是为了实现多态,是在运行时绑定的。因此显然内联函数和多态的特性相违背。
5)静态成员函数
首先静态成员函数理论是可继承的。但是静态成员函数是编译时确定的,无法动态绑定,不支持多态,因此不能被重写,也就不能被声明为虚函数。
答 、O(n^2)
答 、if(x>0.000001&&x<-0.000001)
答 、tcp/ip
应用层/传输层/网络层/数据链路层/物理层
答 、ARP (Address Resolution Protocol)(地址解析协议)
答 、IP地址由两部分组成,网络号和主机号。不过是要和“子网掩码”按位与之后才能区分哪些是网络位哪些是主机位。
思路:循环链表,用取余操作做
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
typedef struct node
{
int data;
node* next;
}node;
void CreatList(node*& head, node*& tail, int n)
{
if (n < 1)
{
head = tail = NULL;
return;
}
head = new node();
head->data = 1;
head->next = NULL;
node* p = head;
for (int i = 2; i < n + 1; i++)
{
p->next = new node();
p = p->next;
p->data = i;
p->next = NULL;
}
tail = p;
tail->next = head;
}
void Print(node*& head)
{
node* p = head;
printf("创建出来的链表为:");
while (p && p->next != head)
{
printf("%d->", p->data);
p = p->next;
}
if (p)
{
printf("%d\n", p->data);
}
}
void CountPrint(node*& head, node*& tail, int m)
{
node* pre = tail;
node* cur = head;
int cnt = m;
printf("取到的M值为: ");
while (cur && cur->next != cur)
{
if (cnt != 1)
{
cnt--;
pre = cur;
cur = cur->next;
}
else
{
printf("%d, ", cur->data);
pre->next = cur->next;
delete cur;
cur = pre->next;
cnt = m;
}
}
if (cur)
{
printf("%d\n", cur->data);
delete cur;
head = tail = NULL;
}
}
int main()
{
node* head;
node* tail;
int m;
int n;
printf("请输入N的值:");
scanf_s("%d", &n);
printf("请输入M的值:");
scanf_s("%d", &m);
CreatList(head, tail, n);
Print(head);
CountPrint(head, tail, m);
system("pause");
return 0;
}
运行效果图:
答 、switch的参数不能为实型。
答、能,局部会屏蔽全局。要用全局变量,需要使用"::"
局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内
答 、可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错
答 、可以,在不同的C文件中以static形式来声明同名全局变量。
可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错
答 、死循环或者说无限循环。和while(1)相同。
答 、前一个循环一遍再判断,后一个判断以后再循环
#include<stdio.h>
void main()
{
int a, b, c, d;
a = 10;
b = a++;
c = ++a;
d = 10 * a++;
printf("b, c, d: %d, %d, %d", b, c, d);
return;
}
运行效果图:
答 、10,12,120
typedef union
{
long i;
int k[5];
char c;
} DATE;//4
struct data
{
int cat; //4
DATE cow;//8
double dog;//8
}too;
DATE max;
void main()
{
printf("结构体cat size:%d\n", sizeof(too.cat));
printf("结构体cow size:%d\n", sizeof(too.cow));
printf("结构体dog size:%d\n", sizeof(too.dog));
printf("结构体size:%d\n", sizeof(too));
printf("联合体max size:%d\n", sizeof(max));
printf("最终结果:%d", sizeof(too) + sizeof(max));
}
执行效果图:
则语句 printf(“%d”,sizeof(too)+sizeof(max));的执行结果是?
答 、结果是:52_。DATE是一个union, 变量公用空间. 里面最大的变量类型是int[5],占用20个字节. 所以它的大小是20
data是一个struct, 每个变量分开占用空间.依次为int4 + DATE20 + double8 =32.
所以结果是 20 + 32 = 52.
当然…在某些16位编辑器下, int可能是2字节,那么结果是 int2 + DATE10 + double8 =20
答 、应该是4^3-1=63
规律是n^3-1(当n为偶数0,2,4)
n^3+1(当n为奇数1,3,5)
答 、设2个栈为A,B,一开始均为空.
入队:
将新元素push入栈A;
出队:
(1)判断栈B是否为空;
(2)如果不为空,则将栈A中所有元素依次pop出并push到栈B;
(3)将栈B的栈顶元素pop出;
这样实现的队列入队和出队的平摊复杂度都还是O(1), 比上面的几种方法要好。
答 、函数名: atol
功 能: 把字符串转换成长整型数
用 法: long atol(const char *nptr);
程序例:
#include<stdio.h>
#include<iostream>
using namespace std;
int char2int(const char *str)
{
int value = 0;
int neg = 0;
switch (*str)
{
case '-':
neg = 1;
/* 这里没有break */
case '+':
str++;
break;
}
while (*str >= '0' && *str <= '9')
{
value *= 10;
value += *str - '0';
str++;
}
return value;
}
long char2long(const char *s)
{
long r = 0;
int neg = 0;
switch (*s)
{
case '-':
neg = 1;
/* 这里没有break */
case '+':
s++;
break;
}
while (*s >= '0' && *s <= '9')
{
int n = *s++ - '0';
if (neg)
n = -n;
r = r * 10 + n;
}
return r;
}
int main(void)
{
long l,i;
const char *str = "9876543210";
i = char2int(str);
l = char2long(str);
printf("string = %s long =%ld\n", str, l);
printf("string = %s integer =%d\n", str, i);
return(0);
}
答 、c用宏定义,c++用inline
#defineSECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在这想看到几件事情:
1). #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)
2). 懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。
3). 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。
4). 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。
#define MIN(A,B) ((A)<= (B) (A) : (B))
这个测试是为下面的目的而设的:
编译程序时,只要遇到 #error 就会跳出一个编译错误,既然是编译错误,要它干嘛呢?其目的就是保证程序是按照你所设想的那样进行编译的。
这个问题用几个解决方案。我首选的方案是:
while(1)
{
}
一些程序员更喜欢如下方案:
for (; ; )
{
}
这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的
基本原理。如果他们的基本答案是:“我被教着这样做,但从没有想到过为什么。”这会给我留下一个坏印象。
第三个方案是用 goto
Loop:
...
goto Loop;
应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。
a) 一个整型数(An integer)
b) 一个指向整型数的指针(A pointer to an integer)
c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer toan integer)
d) 一个有10个整型数的数组(Anarray of 10 integers)
e) 一个有10个指针的数组,该指针是指向一个整型数的(Anarray of 10 pointers to integers)
f) 一个指向有10个整型数数组的指针(Apointer to an array of 10 integers)
g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a functionthat takes an integer as an argument and returns an integer)
h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数(An array of ten pointers to functions that take an integer
argument and returnan integer )
答案是:
a) int a; // Aninteger
b) int *a; // Apointer to an integer
c) int **a; // Apointer to a pointer to an integer
d) int a[10]; // Anarray of 10 integers
e) int *a[10]; // Anarray of 10 pointers to integers
f) int (*a)[10]; // Apointer to an array of 10 integers
g) int (*a)(int); //A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int);// An array of 10 pointers to functions that take an integer argument andreturn an integer
人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。
但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道
所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?
这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:
1)使用Const关键字的地方是为了说明这个参数为常量,是不应该被修改的。
2)合理使用const 可以是编译器自然的保护那些不希望被修改的参数,防止被无意的代码修改。
3)通过给优化器一些有用的信息,使用关键字const也许是代码更加紧凑。
下面的声明都是什么意思?
1)const int a;
2)int const a;
3)const int *a;
4)int * const a;
5)int const * a const;
1) a是一个常整数;
2) a是一个常整数;
3) a是一个指向常整形的指针
4) a是一个指向整型的常指针;
5) a是一个指向常整型的常指针;
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatileint *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的有个恶作剧。这段代码的目的是用来返指针ptr指向值的平方,但是,由于ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatileint *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a + b > 6)puts("> 6") : puts("<= 6");
}
这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是“>6”。原因是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。
int a = 5, b = 7, c;
c = a+++b;
这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:
c = a++ + b;
因此, 这段代码持行后a= 6, b = 7, c = 12。
如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题
#include<stdio.h>
#include<malloc.h>
struct Node
{
int data;
Node *next;
};
void Create(Node **root)
{
Node *p, *q;
int count;
if (NULL == *root)
return;
p = *root;
printf("请输入节点的个数:");
scanf_s("%d", &count);
for (int i = 0; i < count; i++)
{
q = (Node*)malloc(sizeof(Node));
printf("请输入节点数据:");
scanf_s("%d", &(q->data));
p->next = q;
p = q;
p->next = NULL;
}
}
Node * Combine(Node *root1, Node *root2)
{
Node *root3 = (Node*)malloc(sizeof(Node));
Node *p1, *p2, *p3;
p1 = root1->next;
p2 = root2->next;
p3 = root3;
while (p1 != NULL && p2 != NULL)
{
if (p1->data < p2->data)
{
p3->next = p1;
p3 = p3->next;
p1 = p1->next;
}
else
{
p3->next = p2;
p3 = p3->next;
p2 = p2->next;
}
}
while (p1 == NULL && p2 != NULL)
{
p3->next = p2;
p3 = p3->next;
p2 = p2->next;
}
while (p2 == NULL && p1 != NULL)
{
p3->next = p1;
p3 = p3->next;
p1 = p1->next;
}
return root3;
}
int main()
{
Node *root1, *root2, *Combine_Root;
Node *p, *q;
root1 = (Node*)malloc(sizeof(Node));
root2 = (Node*)malloc(sizeof(Node));
Create(&root1);
Create(&root2);
Combine_Root = Combine(root1, root2);
for (p = Combine_Root->next; p != NULL; p = p->next)
printf("%d", p->data);
free(root1);
free(root2);
for (p = Combine_Root; p != NULL; )
{
q = p;
p = p->next;
free(q);
}
return 0;
}
思路:递归的方法,记录当前最大的,并且判断当前的是否比这个还大,大则继续,否则返回false结束:
#include<stdio.h>
bool charge(int p[], int n)
{
if (n == 1)
return true;
else
{
if (p[n - 1] > p[n - 2])
{
return charge(p, n - 1);
}
else
return false;
}
}
int main()
{
int a[5] = { 1,2,3,4,5 };
bool flag = false;
flag = charge(a, 5);
if (flag)
printf("yes\n");
else
printf("false\n");
system("pause");
return 0;
}
用外部排序,在《数据结构》书上有《计算方法导论》在找到第n大的数的算法上加工
同学的4道面试题,应聘的职位是搜索引擎工程师,后两道超级难,(希望大家多给一些算发)
1.给两个数组和他们的大小,还有一动态开辟的内存,求交集,把交集放到动态内存dongtai,并且返回交集个数
long jiaoji(longa[],long b[],long alength,long blength,long* dongtai[])
2.单连表的建立,把’a’–'z’26个字母插入到连表中,并且倒叙,还要打印!
方法1:
typedef struct val
{
int date_1;
struct val *next;
}*p;
void main()
{
char c;
for (c = 122; c >= 97; c--)
{
p.date = c;
p = p->next;
}
p.next = NULL;
}
方法2:
void main()
{
node *p = NULL;
node *q = NULL;
node *head = (node*)malloc(sizeof(node));
head->data = ''; head->next = NULL;
node *first = (node*)malloc(sizeof(node));
first->data = 'a'; first->next = NULL; head->next = first;
p = first;
int longth = 'z' - 'b';
int i = 0;
while (i <= longth)
{
node *temp = (node*)malloc(sizeof(node));
temp->data = 'b' + i; temp->next = NULL; q = temp;
head->next = temp; temp->next = p; p = q;
i++;
}
print(head);
}
象搜索的输入信息是一个字符串,统计300万输入信息中的最热门的前十条,我们每次输入的一个字符串为不超过255byte,内存使用只有1G,
请描述思想,写出算发(c语言),空间和时间复杂度,
7.国内的一些帖吧,如baidu,有几十万个主题,假设每一个主题都有上亿的跟帖子,怎么样设计这个系统速度最好,请描述思想,写出算发(c语言),空间和时间复杂度,
#include <string.h>
void main(void)
{
char *src = "hello,world";
char *dest = NULL;
dest = (char *)malloc(strlen(src));
int len = strlen(str);
char *d = dest;
char *s = src[len];
while (len-- != 0)
d++ = s--;
printf("%s", dest);
}
找出错误!!
#include "string.h"
#include"stdio.h"
#include"malloc.h"
void main(void)
{
char *src = "hello,world";
char *dest = NULL;
dest = (char *)malloc(sizeof(char)*(strlen(src) + 1));
int len = strlen(src);
char *d = dest;
char *s = src + len - 1;
while (len-- != 0)
*d++ = *s--;
*d = '\0';
printf("%s", dest);
}
bool IsSymmetry(const char* p)
{
assert(p != NULL);
const char* q = p;
int len = 0;
while (*q++ != '\0')
{
len++;
}
bool bSign = true;
q = p + len - 1;
if (0 < len)
{
for (int i = 0; i < len / 2; i++)
{
if (*p++ != *q--) { bSign = false; break; };
}
}
if (bSign == true)
{
printf("Yes!\n");
}
else
{
printf("No!\n");
}
return bSign;
}
全称Point to Point Protocol over Ethernet,意思是基于以太网的点对点协议。实质是以太网和拨号网络之间的一个中继协议,所以在网络中,它的物理结构与原来的LAN接入方式没有任何变化,只是用户需要在保持原接入方式的基础上,安装一个PPPoE客户端(这个是通用的)。之所以采用该方式给小区计时/计流量用户,是方便计算时长和流量。此类用户在使用上比包月用户增加了PPPoE虚拟拨号的过程。电信的ADSL接入也是需要安装使用PPPoE。
预编译又称为预处理,是做些代码文本的替换工作。处理#开头的指令,比如拷贝#include包含的文件代码,#define宏定义的替换,条件编译等,就是为编译做的预备工作的阶段,主要处理#开始的预编译指令,预编译指令指示了在程序正式编译前就由编译器进行的操作,可以放在程序中的任何位置。
c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:1)宏定义 2)文件包含 3)条件编译
1、总是使用不经常改动的大型代码体。
2、程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。
什么是进程(Process):普通的解释就是,进程是程序的一次执行,而什么是线程(Thread),线程可以理解为进程中的执行的一段程序片段。在一个多任务环境中下面的概念可以帮助我们理解两者间的差别:
进程间是独立的,这表现在内存空间,上下文环境;线程运行在进程空间内。 一般来讲(不使用特殊技术)进程是无法突破进程边界存取其他进程内的存储空间;而线程由于处于进程空间内,所以同一进程所产生的线程共享同一内存空间。 同一进程中的两段代码不能够同时执行,除非引入线程。线程是属于进程的,当进程退出时该进程所产生的线程都会被强制退出并清除。线程占用的资源要少于进程所占用的资源。 进程和线程都可以有优先级。在线程系统中进程也是一个线程。可以将进程理解为一个程序的第一个线程。
线程是指进程内的一个执行单元,也是进程内的可调度实体与进程的区别:
(1)地址空间:进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;
(2)进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
(3)线程是处理器调度的基本单位,但进程不是.
(4)二者均可并发执行.
参考回答:
1)进程是cpu资源分配的最小单位,线程是cpu调度的最小单位。
2)进程有独立的系统资源,而同一进程内的线程共享进程的大部分系统资源,包括堆、代码段、数据段,每个线程只拥有一些在运行中必不可少的私有属性,比如tcb,线程Id,栈、寄存器。
3)一个进程崩溃,不会对其他进程产生影响;而一个线程崩溃,会让同一进程内的其他线程也死掉。
4)进程在创建、切换和销毁时开销比较大,而线程比较小。进程创建的时候需要分配系统资源,而销毁的的时候需要释放系统资源。进程切换需要分两步:切换页目录、刷新TLB以使用新的地址空间;切换内核栈和硬件上下文(寄存器);而同一进程的线程间逻辑地址空间是一样的,不需要切换页目录、刷新TLB。
5)进程间通信比较复杂,而同一进程的线程由于共享代码段和数据段,所以通信比较容易。
插入排序基本思想:(假定从大到小排序)依次从后面拿一个数和前面已经排好序的数进行比较,比较的过程是从已经排好序的数中最后一个数开始比较,如果比这个数,继续往前面比较,直到找到比它大的数,然后就放在它的后面,如果一直没有找到,肯定这个数已经比较到了第一个数,那就放到第一个数的前面。那么一般情况下,对于采用插入排序法去排序的一组数,可以先选 取第一个数做为已经排好序的一组数。然后把第二个放到正确位置。
选择排序(Selection Sort)是一种简单直观的排序算法。它的工作原理如下。首先在未排序序列中找到最小元素,存放到排序序列的起始位置,然后,再从剩余未排序元素中继续寻找最小元素,然后放到排序序列末尾。以此类推,直到所有元素均排序完毕。
能正确表示a和b同时为正或同时为负的逻辑表达式是(D )。
A、(a>=0||b>=0)&&(a<0||b<0)
B、(a>=0&&b>=0)&&(a<0&&b<0)
C、(a+b>0)&&(a+b<=0)
D、a*b>0
以下关于运算符优先顺序的描述中正确的是©。
A、关系运算符<算术运算符<赋值运算符<逻辑与运算符
B、逻辑与运算符<关系运算符<算术运算符<赋值运算符
C、赋值运算符<逻辑与运算符<关系运算符<算术运算符
D、算术运算符<关系运算符<赋值运算符<逻辑与运算符
写一个函数将"tom is cat" 倒序打印出来,即 “cat is tom”
#include <stdio.h>
#define SPACE ' '
void main()
{
const char* str = "Tom is cat"; // 字符串
char* p1 = (char *)str + strlen(str) - 1;
char* p2 = p1+1; // 开始时,p1,p2都指向字符串结尾处
char t = 0; // 临时变量,用来保存被临时替换为ENDL的字符
while (str != p1--)
{
if (SPACE == *p1)
{
// p1+1指向单词的第一个字母,p2指向单词的结尾,此时输出这个单词
for (int i = 1; p1 + i != p2; ++i)
{
cout << *(p1 + i);
}
cout << ' ';
p2 = p1;
}
if (str == p1)
{
for (int i = 0; p1 + i != p2; ++i)
{
cout << *(p1 + i);
}
cout << endl;
}
}
}
1)写一个递归函数将内存中的字符串翻转"abc"->“cba”
void str_reverse(char* p1, char* p2)
{
if (p1 == p2)
return;
*p1 = (*p1) + (*p2);
*p2 = (*p1) - (*p2);
*p1 = (*p1) - (*p2);
if (p1 == p2 - 1)
return;
else
str_reverse(++p1, --p2);
}
void main()
{
char ptest[] = "abcdefg";
str_reverse(ptest, ptest + strlen(ptest) -1);
printf(ptest);
}
2)写一个函数将"tom is cat" 将内存中的字符串翻转,即 “cat istomm”
#include <stdio.h>
#define SPACE ' '
#define ENDL '\0'
char s[] = "The quick brown fox jumps over the lazy dog";
void str_reverse(char* p1, char* p2)
{
if (p1 == p2)
return;
*p1 = (*p1) + (*p2);
*p2 = (*p1) - (*p2);
*p1 = (*p1) - (*p2);
if (p1 == p2 - 1)
return;
else
str_reverse(++p1, --p2);
}
void str_word_reverse(char* str)
{
char *q1 = str, *q2 = str, *t;
while (*q1 == SPACE)
q1++;
if (*q1 == ENDL)
return; //!
else
q2 = q1 + 1;
while ((*q2 != SPACE) && (*q2 != ENDL))
q2++;
t = q2--;
str_reverse(q1, q2);
if (*t == ENDL)
return;
else
str_word_reverse(t);
}
int main(int a, char** b)
{
printf("%s\n", s);
str_reverse(s, s + strlen(s) - 1);
printf("%s\n", s);
str_word_reverse(s);
printf("%s\n", s);
return 0;
}
Output:
The quick brown foxjumps over the lazy dog
god yzal eht revo spmuj xof nworb kciuq ehT
dog lazy the over jumps fox brown quick The
今天同学又问一道题,和上面有些类似,但是要求更严格了一些:
写一个递归函数将内存中的字符串翻转"abc"->“cba”,并且函数原型已确定:void reverse(char* p)
其实,要求越多,思路越确定,我的解如下:
#include <stdio.h>
#include <string.h>
char s[] = "0123456789";
#define ENDL '\0'
void reverse(char* p)
{
//这是这种方法的关键,使用static为的是能用str_reverse的思路,但是不好
static char* x = 0;
if (x == 0)
x = p;
char* q = x + strlen(p) - 1;
if (p == q)
return;
*q = (*p) ^ (*q);
*p = (*p) ^ (*q);
*q = (*p) ^ (*q);
if (q == p + 1)
return;
reverse(++p);
}
//这种方法就直观多了,但是当字符串很长的时候就很低效
void reverse2(char* p)
{
if (*(p + 1) == ENDL)
return;
char* o = p + strlen(p) - 1;
char t = *o;
for (; o != p; o--)
*o = *(o - 1);
*p = t;
reverse2(p + 1);
}
int main(int c, char**argv)
{
reverse2(s);
printf("%s\n", s);
return 0;
}
交换两个参数值的宏定义为:
#define SWAP(a,b) (a)=(a)+(b);(b)=(a)-(b);(a)=(a)-(b);
游标和指针
我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“–”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。
从语法上,在C++中(只讨论C++中)。class和struct做类型定义时只有两点区别:
(一)默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;
(二)成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。
除了这两点,class和struct基本就是一个东西。语法上没有任何其它区别。
不能因为学过C就总觉得连C++中struct和class都区别很大,下面列举的说明可能比较无聊,因为struct和class本来就是基本一样的东西,无需多说。但这些说明可能有助于澄清一些常见的关于struct和class的错误认识:
(1)都可以有成员函数;包括各类构造函数,析构函数,重载的运算符,友元类,友元结构,友元函数,虚函数,纯虚函数,静态函数;
(2)都可以有一大堆public/private/protected修饰符在里边;
(3)虽然这种风格不再被提倡,但语法上二者都可以使用大括号的方式初始化:
A a = {1, 2, 3};不管A是个struct还是个class,前提是这个类/结构足够简单,比如所有的成员都是public的,所有的成员都是简单类型,没有显式声明的构造函数。
(4)都可以进行复杂的继承甚至多重继承,一个struct可以继承自一个class,反之亦可;一个struct可以同时继承5个class和5个struct,虽然这样做不太好。
(5)如果说class的设计需要注意OO的原则和风格,那么没任何理由说设计struct就不需要注意。
(6)再次说明,以上所有说法都是指在C++语言中,至于在C里的情况,C里是根本没有“class”,而C的struct从根本上也只是个包装数据的语法机制。
最后,作为语言的两个关键字,除去定义类型时有上述区别之外,另外还有一点点:“class”这个关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。
返回值类型不同构不成重载
参数参数顺序不同能构成重载
c++函数同名不同返回值不算重载!函数重载是忽略返回值类型的。
成员函数被重载的特征有:
关系数据库是表的集合,它是由一个或多个关系模式定义。SQL语言中的数据定义功能包括对数据库、基本表、视图、索引的定义。
关系数据库以关系模型为基础,它有以下三部分组成:
●数据结构——模型所操作的对象、类型的集合
●完整性规则——保证数据有效、正确的约束条件
●数据操作——对模型对象所允许执行的操作方式
关系(Relation)是一个由行和列组成的二维表格,表中的每一行是一条记录(Record),每一列是记录的一个字段(Field)。表中的每一条记录必须是互斥的,字段的值必须具有原子性。
SQL(结构化查询语言)是关系数据库语言的一种国际标准,它是一种非过程化的语言。通过编写SQL,我们可以实现对关系数据库的全部操作。
●数据定义语言(DDL)——建立和管理数据库对象
●数据操纵语言(DML)——用来查询与更新数据
●数据控制语言(DCL)——控制数据的安全性
起来是一个很简单的问题,每一个使用过RDBMS的人都会有一个概念。
事务处理系统的典型特点是具备ACID特征。ACID指的是Atomic(原子的)、Consistent(一致的)、Isolated(隔离的)以及Durable(持续的),它们代表着事务处理应该具备的四个特征:
顺序结构
选择结构
循环结构
cvs(Concurrent Version System) 是一个版本控制系统。使用它,可以记录下你的源文件的历史。
例如,修改软件时可能会不知不觉混进一些 bug,而且可能过了很久你才会察觉到它们的存在。有了 cvs,你可以很容易地恢复旧版本,并从中看出到底是哪个修改导致了这个bug。有时这是很有用的。
CVS服务器端对每个文件维护着一个修订号,每次对文件的更新,都会使得文件的修订号加1。在客户端中也对每个文件维护着一个修订号,CVS通过这两个修订号的关系,来进行Update,Commit和发现冲突等操作操作
按照数据结构类型的不同,将数据模型划分为层次模型、网状模型和关系模型。
工程模式即将对象创建过程封装即为工厂模式。
单例模式即整个类只有一个对象,并且不允许显示创建。
1)vector底层实现是数组;list是双向 链表。
2)vector支持随机访问,list不支持。
3)vector是顺序内存,list不是。
4)vector在中间节点进行插入删除会导致内存拷贝,list不会。
5)vector一次性分配好内存,不够时才进行2倍扩容;list每次插入新节点都会进行内存申请。
6)vector随机访问性能好,插入删除性能差;list随机访问性能差,插入删除性能好。
vector拥有一段连续的内存空间,因此支持随机访问,如果需要高效的随即访问,而不在乎插入和删除的效率,使用vector。
list拥有一段不连续的内存空间,如果需要高效的插入和删除,而不关心随机访问,则应使用list。
在类内部添加一个虚拟函数表指针,该指针指向一个虚拟函数表,该虚拟函数表包含了所有的虚拟函数的入口地址,每个类的虚拟函数表都不一样,在运行阶段可以循此脉络找到自己的函数入口。
纯虚函数相当于占位符,先在虚函数表中占一个位置由派生类实现后再把真正的函数指针填进去。除此之外和普通的虚函数没什么区别。
抽象类中的纯虚函数没有具体的实现,所以没办法实例化。
在函数后面加个const一般在类的成员函数中使用,表示这个函数不修改数据成员的值。
另:void set_prt_val(int val)const {*ptr= val}理解:指针ptr指向的内容不是类的数据成员,所以这可这么写:*ptr = val;但这个指针在这个函数中不能修改。如果写成这样:ptr = &i(假设i是另外一个整形变量)就不对了,因为改变了指针的内容。
线程间通信类型:
(1)全局数据;
(2)全局变量;
(3)全局数据结构;
(4)线程间通信的参数:pThread_create这类API接口中的参数
答:编译器自动对齐的原因:为了提高程序的性能,数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;然而,对齐的内存访问仅需要一次访问。
答:
TCP:服务器端:1.socket()建立套接字,2将套接字绑定到本地地址和端口上,绑定(bind)3.将套接字设为监听模式,准备接收客户端,监听(listen);4.等待客户端请求到来,请求到来后,连接请求,并返回一个新的对应此连接的套接字,accept()5.用返回的套接字和客户端进行通讯(send/recv);6.返回并等待另一客户请求。7.关闭套接字。
客户端:1.socket()建立套接字2.向服务器发出连接请求,(connect)2。和服务器进行通信,send()和recv(),在套接字上写读数据,直至数据交换完毕;4closesocket()关闭套接字。
UDP:1服务器端:1.创建套接字(socekt)2.将套接字绑定到本地地址和端口上(bind);3.等待接收数据(recvfrom);4.closesocket()关闭套接字。
客户端:1.创建套接字(socekt)2,向服务器端发送数据(sendto)3.closesocket()关闭套接字。
答:(1)可用来创建动态增长和减小的数据结构
(2)它是类型无关的,因此具有很高的可复用性。
(3)它在编译时而不是运行时检查数据类型,保证了类型安全
(4)它是平台无关的,可移植性
(5)可用于基本数据类型
答:调用一个DLL中的函数有两种方法:
1.载入时动态链接(load-time dynamiclinking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2.运行时动态链接(run-time dynamiclinking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。
答:同步多个线程对一个数据类的同时访问
已知strcat函数的原型是char *strcat (char strDest, const charstrSrc);
其中strDest 是目的字符串,strSrc 是源字符串。
(1)不调用C++/C 的字符串库函数,请编写函数strcat
答:
VC源码:
char * __cdecl strcat(char * dst, const char * src)
{
assert((dst != NULL) && (src != NULL));
char * cp = dst;
while (*cp)
cp++; /* find end of dst */
while ( (*cp++ = *src++) != '\0'); /* Copy src to end of dst */
return dst; /* return dst */
}
(2)strcat能把strSrc的内容连接到strDest,为什么还要char * 类型的返回值?
答:方便赋值给其他变量
已知类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(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;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。