My C++ Study Notes

* 插入排序

#include <iostream>
using namespace std;

/*交换函数,作用是交换数组中的两个元素的位置*/
void swap(int array[],int i,int j)
{
	int tmp=array[i];
	array[i]=array[j];
	array[j]=tmp;
}

/*插入排序*/
void InsertSort(int array[],int n)
{
	for(int i=1;i<n;i++)
	{
		for(int j=i;j>0;j--)
		{
			if(array[j]>array[j-1])
				swap(array,j,j-1);
			else
			    break;
		}
	}
}

int main()
{
	int array[5]={3,1,2,5,4};
	InsertSort(array,5);
	for(int i=0;i<5;i++)
		cout<<array[i]<<"  ";
	cout<<endl;
	return 0;
}

* 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然)

typedef struct _Node
{
	_Node* pNext;
	int data;
}NODE, *PNODE;
bool Check(const PNODE pn)
{
	if (NULL == pn) return false;
	PNODE p1 = pn;
	PNODE p2 = p1->pNext;
	while(p1 != NULL && p2->pNext != NULL)
	{
		p1 = p1->pNext;
		p2 = p2->pNext->pNext;
		if (p1 == p2) return true;
		 return false;
	}
}

* 写一个函数找出一个整数数组中,第二大的数

int FindSecMax(int data[], int n)
{
	int nMax = -32767;
	int nSecMax = -32767;
	for (int i = 0; i < n; i++)
	{
		if (data[i] > nMax)
		{
			nSecMax = nMax;
			nMax = data[i];
		}
		else
		{
			if (data[i] > nSecMax)
			{
				nSecMax = data[i];
			}
		}
	}
	return nSecMax;
}

* pStr是指向以'\0'结尾的字符串的指针,steps是要求移动的n

void LoopMove ( char*pStr, int steps )
{
 int n = strlen( pStr ) - steps;
 char tmp[MAX_LEN]; 
 strcpy ( tmp, pStr + n ); 
 strcpy ( tmp + steps, pStr); 
 *( tmp + strlen ( pStr ) ) ='\0';
 strcpy( pStr, tmp );
}

* 已知两个链表head1 和head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。

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 ;
}

* 已知链表的头结点head,写一个函数把这个链表逆序

struct Node
{
	int data;
	Node *pNext;
};
typedef struct Node Node;
Node* ReverseList(Node* pHead)
{
	if (pHead == NULL || pHead->pNext == NULL) return pHead;
	Node* p1 = pHead;
	Node* p2 = pHead->pNext;
	Node* p3 = p2->pNext;
	p1->pNext = NULL;
	while (p3 != NULL)
	{
		p2->pNext = p1;
		p1 = p2;
		p2 = p3;
		p3 = p3->pNext;
	}
	p2->pNext = p1;
	pHead = p2;
	return pHead;
}
* derived class templates类通过this->指向base class templates 内的成员名称,或明白写出base class资格修饰符完成。

* 声明template参数时,前缀关键字class和typename可以互换。用typename标志嵌套从属类型名称。但不能在base class lists和number initialization list中作为base class修饰符。

* 多继承时,如果父类有相同函数,子类再调用时可能产生歧义,不知道调用哪个父类的函数,这时候可以使用虚继承方式声明继承。

* 不要重新定义一个继承而来的缺省的参数值,因为缺省参数值都是静态绑定,而virtual函数是动态绑定。

* 不要重新定义继承而来的non-virtual函数。

* 纯虚函数只指定接口继承。虚函数指定接口继承和缺省实现继承。non-virtual函数指定接口继承和强制实现继承。

* 子类可隐藏父类的函数,若要使用父类的函数可以使用using声明。

* public继承意味is-a.适用于base class身上的每一件事情也一定适用于derived class身上,因为每一个derived class对象也是一个base class对象。

* inline函数限制在小型,被频繁调用的函数身上。

* const_cast去除对象的const属性。dynamic_cast主要用来向下安全转型。reinterpret_cast意图执行低级转型,如将*int转为int。static_cast执行强迫隐式转换,如将Non-const转为const对象。将int转为double,将void*转为typed指针,将pointer to base 转为pointer to derived等。

* 之所以需要dynamic_cast通常是因为想在一个derived class对象上执行derived class操作函数,但手上只有一个指向base的pointer或reference。

* 定义变量是应该延迟到能给它赋初值实参为止。

* 如果你需要为某个函数的所有参数进行类型转换,那么这个函数必须是个non-remember。

* namespace可以跨域多个源码文件而class不行。可以用No remember no friend替换class member函数,这样可以增加封装性。

* 成员变量为private,封装性。

* 不要返回指向局部变量的指针或引用。或指向对内存(new)的引用。或指向local static的指针,引用。

* C++默认以传值方式传递参数和返回值,这些拷贝的值以拷贝构造函数产生。值传递可能会造成对象切割。如果参数是父类,传递的的是之类对象,那么传递后就变成父类的对象了。最好以传递const reference传递参数。

* 普遍RAII class coping行为是:禁止coping,实施引用记数法。

* 拷贝构造函数拷贝赋值语句应该确保复制对象内所有成员变量和所有base class部分。深拷贝。

* 不要尝试以某个coping函数调用另一个coping函数,应该将共同部分放进第三个函数中,供两个coping函数调用。

* 令赋值操作符(=,+=,-= ...)返回一个reference to *this

* 在derived class对象的base class构造期间,对象类型是base class而不是derived class。因此在构造和析构期间不要调用virtual函数。

* 如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常。那么这个异常必须来自析构函数以外的某个函数。因为析构函数突出异常就是危险,带来不确定的行为。class应提供一个普通函数执行可能抛出异常的操作。

* 尽量以const, enum, inline替换#define

* 对于单纯常量,最好以const对象或enum代替#defines

* 对于形式函数的宏,最好改用inline函数替换#defines

* 将某些东西声明为const可以帮助编译器侦测出错误用法,const可以被施加于在任何作用域内的对象,成员函数,函数返回类型,成员函数体。

* 但const和no-const成员函数有着实质等价的实现时,令non-const版本调用const版本可以避免代码重复。

* 自定义对象的初始化应该在构造函数的初始化成员列表中完成,因为这个动作早于构造函数的执行,这样做效率也高。初始化成员列表应该和他们的声明顺序相同。

* 为对象进行手工初始化,因为C++并不保证他们初始化。

* 最好用函数内的local static变量替换non-local static对象(如全局对象)。函数返回这个对象的引用即可。

* 带有多态性质的base class应该有一个virtual西沟函数,如果class有任何virtual函数,就应该有一个virtual析构函数。如果class不作为base class使用,就不应该有virtual函数,因为有virtual函数就要有虚函数表,增加存储空间。

#define的名称没有进入编译器的的符号表,如#define AA 11,如果出错,错误信息是11而不是AA,调试困难。可以这样做:const int MY_AA = 11;

头文件类的定义中可以怎样写

static const int num = 5;
如果类的专属常量又是static且为整数,可以直接声明,不用写定义式。但如果不是,在cpp内需要声明,头文件中定义.

一. const 有什么用途?

(1)可以定义const常量。
(2)修饰函数的返回值和形参。
(3)修饰函数的定义体,定义类的const成员函数。被const修饰的东西受到强制保护,可以预防意外的变动,提高了程序的健壮性。

二.关于sizeof小结

(1) sizeof计算的是在栈中分配的内存大小。
(2) sizeof不计算static变量占得内存。
(2) 指针的大小一定是4个字节,而不管是什么类型的指针。
(3) char型占1个字节,int占4个字节,short int占2个字节。
(4) long int占4个字节,float占4字节,double占48字节,string占4字节。
(5) 一个空类占1个字节,单一继承的空类占1个字节,虚继承涉及到虚指针所以占4个字节。

(6) 数组的长度:
 若指定了数组长度,则不看元素个数,总字节数=数组长度*sizeof(元素类型)。
 若没有指定长度,则按实际元素个数类确定。
 PS:若是字符数组,则应考虑末尾的空字符。
(7)结构体对象的长度
 在默认情况下,为方便对结构体内元素的访问和管理,当结构体内元素长度小于处理器位数的时候,便以结构体内最长的数据元素的长度为对齐单位,即为其整数倍。若结构体内元素长度大于处理器位数则以处理器位数为单位对齐。
(8)unsigned影响的只是最高位的意义,数据长度不会改变,所以sizeof(unsigned int)= 4。
(7)自定义类型的sizeof取值等于它的类型原型取sizeof。
(8)对函数使用sizeof,在编译阶段会被函数的返回值的类型代替。
(9)sizeof后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为sizeof是运算符。
(10) 当使用结构类型或者变量时,sizeof返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof不能返回动态数组或者外部数组的尺寸。

三.sizeof与strlen的区别?

(1)sizeof的返回值类型为size_t(unsigned int);
(2)sizeof是运算符,而strlen是函数;
(3)sizeof可以用类型做参数,其参数可以是任意类型的或者是变量、函数,而strlen只能用char*做参数,且必须是以’\0’结尾;
(4)数组作sizeof的参数时不会退化为指针,而传递给strlen是就退化为指针;
(5)sizeof是编译时的常量,而strlen要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小;

四.指针和引用的区别?

指针和引用都提供了间接操作对象的功能。
(1) 指针定义时可以不初始化,而引用在定义时就要初始化,和一个对象绑定,而且一经绑定,只要引用存在,就会一直保持和该对象的绑定;
(2) 赋值行为的差异:指针赋值是将指针重新指向另外一个对象,而引用赋值则是修改对象本身;
(3) 指针之间存在类型转换,而引用分const引用和非const应用,非const引用只能和同类型的对象绑定,const引用可以绑定到不同但相关类型的对象或者右值
(4)不存在指向空值的引用,但存在指向控制的指针;
(5)引用是某个对象的别名,主要用来描述函数和参数和返回值。而指针与一般的变量是一样的,会在内存中开辟一块内存。
如果函数的参数或返回值是类的对象的话,采用引用可以提高程序的效率。

五.数组和指针的区别?

(1)数组要么在全局数据区被创建,要么在栈上被创建;指针可以随时指向任意类型的内存块;
(2)修改内容上的差别:

char a[] = “hello”;
a[0] = ‘X’;
char *p = “world”; // 注意p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误
(3)用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是p 所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

六.空指针和悬垂指针的区别?

空指针是指被赋值为NULL的指针;delete指向动态分配对象的指针将会产生悬垂指针。
(1) 空指针可以被多次delete,而悬垂指针再次删除时程序会变得非常不稳定;
(2) 使用空指针和悬垂指针都是非法的,而且有可能造成程序崩溃,如果指针是空指针,尽管同样是崩溃,但和悬垂指针相比是一种可预料的崩溃。

七.C++中有malloc/free,为什么还有new/delete?

malloc/free是C/C++标准库函数,new/delete是C++运算符。他们都可以用于动态申请和释放内存。
对于内置类型数据而言,二者没有多大区别。malloc申请内存的时候要制定分配内存的字节数,而且不会做初始化;new申请的时候有默认的初始化,同时可以指定初始化;
对于类类型的对象而言,用malloc/free无法满足要求的。对象在创建的时候要自动执行构造函数,消亡之前要调用析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制之内,不能把执行构造函数和析构函数的任务强加给它,因此,C++还需要new/delete。

因此当你分配普通类型的数组时,如 int *a=new[ 10 ];
那么 delete a 和 delete [] a 是一样的.

八.什么是智能指针?

当类中有指针成员时,一般有两种方式来管理指针成员:一是采用值型的方式管理,每个类对象都保留一份指针指向的对象的拷贝;另一种更优雅的方式是使用智能指针,从而实现指针指向的对象的共享。
智能指针的一种通用实现技术是使用引用计数。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象共享同一指针。
每次创建类的新对象时,初始化指针并将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,构造函数减少引用计数(如果引用计数减至0,则删除基础对象)。

九.C++空类默认有哪些成员函数?

默认构造函数、析构函数、拷贝构造函数,拷贝赋值函数

十.继承层次中,为什么基类析构函数是虚函数?

编译器总是根据类型来调用类成员函数。但是一个派生类的指针可以安全地转化为一个基类的指针。这样删除一个基类的指针的时候,C++不管这个指针指向一个基类对象还是一个派生类的对象,调用的都是基类的析构函数而不是派生类的。如果你依赖于派生类的析构函数的代码来释放资源,而没有重载析构函数,那么会有资源泄漏。

十一.为什么构造函数不能为虚函数?

虚函数采用一种虚调用的方法。虚调用是一种可以在只有部分信息的情况下工作的机制。如果创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。

十二.如果虚函数是有效的,那为什么不把所有函数设为虚函数?

不行。虚函数是有代价的,由于每个虚函数的对象都要维护一个虚函数表,因此在使用虚函数的时候都会产生一定的系统开销,这是没有必要的。

十三.重载和覆盖有什么区别?

虚函数是基类希望派生类重新定义的函数,派生类重新定义基类虚函数的做法叫做覆盖;
重载就在允许在相同作用域中存在多个同名的函数,这些函数的参数表不同。重载的概念不属于面向对象编程,编译器根据函数不同的形参表对同名函数的名称做修饰,然后这些同名函数就成了不同的函数。
重载的确定是在编译时确定,是静态的;虚函数则是在运行时动态确定。

十四.const成员,引用成员只能用构造函数初始化列表而不能用赋值语句初始化。

十五.什么是虚指针?

虚指针或虚函数指针是虚函数的实现细节。带有虚函数的每一个对象都有一个虚指针指向该类的虚函数表。

十六.C++如何阻止一个类被实例化?一般在什么时候将构造函数声明为private?

(1)将类定义为抽象基类或者将构造函数声明为private;
(2)不允许类外部创建类对象,只能在类内部创建对象;

十七.main函数执行之前会执行什么?执行之后还能执行代码吗?

(1)全局对象的构造函数会在main函数之前执行;
(2)可以,可以用atexit 注册一个函数,它会在main 之后执行;

int atexit(void (*pFun)(void));
void Fun1(void)
{
	printf("Fun1\n");
}
void main()
{
	atexit(Fun1);
	printf("main\n");
}
十八.进程和线程的区别?
(1)进程是程序的一次执行,线程是进程中的执行单元;
(2)进程间是独立的,这表现在内存空间、上下文环境上,线程运行在进程中;
(3)一般来讲,进程无法突破进程边界存取其他进程内的存储空间;而同一进程所产生的线程共享内存空间;
(4)同一进程中的两段代码不能同时执行,除非引入多线程。

十九.TCP和UDP有什么区别?

TCP——传输控制协议,提供的是面向连接、可靠的字节流服务。

当客户和服务器彼此交换数据前,必须先在双方之间建立一个TCP连接,之后才能传输数据。TCP提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据能从一端传到另一端。
UDP——用户数据报协议,是一个简单的面向数据报的传输层协议。UDP不提供可靠性,它只是把应用程序传给IP层的数据报发送出去,但是并不能保证它们能到达目的地。由于UDP在传输数据报前不用在客户和服务器之间建立一个连接,且没有超时重发等机制,故而传输速度很快.
TCP协议和UDP协议的一些特性区别如下:
1.TCP协议在传送数据段的时候要给段标号;UDP 协议不需要。
2.TCP协议可靠;UDP协议不可靠。
3.TCP协议是面向连接;UDP协议采用无连接。
4.TCP协议负载较高,采用虚电路;UDP协议低负载。
5.TCP协议的发送方要确认接受方是否收到数据段(3次握手协议)。
6.TCP协议采用窗口技术和流控制。

二十.如何编写套接字?

Sercer端:

设置所需Socket的版本:

	WORD wVersionRequested;
	WSADATA wsaData;
	int nError;
	wVersionRequested = MAKEWORD(2, 2);
	nError = WSAStartup(wVersionRequested, &wsaData);
	if (nError != 0)
	{
		std::cout<<"WSAStartup Error"<<std::endl;
		return 0;
	}

创建Socket:

	SOCKET socketSrv = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == socketSrv)
	{
		std::cout<<"socket init error!"<<std::endl;
		return 0;
	}
将Socket绑定在指定端口上:

	SOCKADDR_IN sockAddrIn;
	sockAddrIn.sin_addr.s_addr = htonl(INADDR_ANY);
	sockAddrIn.sin_family = AF_INET;
	sockAddrIn.sin_port = htons(6000);
	bind(socketSrv, (SOCKADDR*)&sockAddrIn, sizeof(SOCKADDR));
Server开始监听:

if ( listen( socketSrv, SOMAXCONN ) == SOCKET_ERROR ) {
	printf( "Listen failed with error: %ld\n", WSAGetLastError() );
	closesocket(socketSrv);
	WSACleanup();
	return 1;
}
开始接受来自客户端的请求,每接受一个客户端的连接就新产生一个Socket对象
		SOCKET client;
		SOCKADDR_IN sockAddrFrom;
		int nFromLen = sizeof(sockAddrFrom);
		client = accept(socketSrv, (struct sockaddr*)&sockAddrFrom, &nFromLen);
客户端:

设置Socket版本:

	WSADATA wsadata;
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
	{
		AfxMessageBox(_T("WSAStartup(MAKEWORD(2, 2), &wsadata) error!"));
		bIsError = TRUE;
	}
创建Socket对象:

	m_socketClient = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == m_socketClient)
	{
		AfxMessageBox(_T("socket(AF_INET, SOCK_STREAM, 0) error!"));
		bIsError = TRUE;
	}

与服务器指定的端口进行连接:

	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(6000);
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.29.83");
	int nRes = connect(m_socketClient, (sockaddr*)&addr, sizeof(addr));
使用Send或者recv方法向服务器端发送数据:

int nRes = recv(pThis->m_socketClient, szBuf, MAX_LENGTH, 0);
send(m_socketClient, pChar, nSize, 0); 

Note:

服务器端也使用Send或者recv方法向指定的客户端发送数据。

一般Socket都使用非阻塞的异步方法,即服务器的accept方法不阻塞当前线程,在新的线程中使用While循环不断accept新的连接。创建非阻塞的方法:

	int r = ioctlsocket(socketSrv, FIONBIO, &ul); //Set socket to nonblocking mode.
	if (SOCKET_ERROR == r)
	{
		std::cout<<"ioctlsocketerror!"<<std::endl;
		return 0;
	}
二十.经常要操作的内存分为那几个类别?
(1)栈区:由编译器自动分配和释放,存放函数的参数值、局部变量的值等;
(2)堆:一般由程序员分配和释放,存放动态分配的变量;
(3)全局区(静态区):全局变量和静态变量存放在这一块,初始化的和未初始化的分开放;
(4)文字常量区:常量字符串就放在这里,程序结束自动释放;
(5)程序代码区:参访函数体的二进制代码。
二十一.堆和栈的区别?
(1)申请方式不同。栈上有系统自动分配和释放;堆上有程序员自己申请并指明大小;
(2)栈是向低地址扩展的数据结构,大小很有限;堆是向高地址扩展,是不连续的内存区域,空间相对大且灵活;
(3)栈由系统分配和释放速度快;堆由程序员控制,一般较慢,且容易产生碎片;
二十二.类使用static成员的优点,如何访问?
优点:
(1)static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突;
(2)可以实施封装。static 成员可以是私有成员,而全局对象不可以;
(3) static 成员是与特定类关联的,可清晰地显示程序员的意图。
static 数据成员必须在类定义体的外部定义(正好一次),static 关键字只能用于类定义体内部的声明中,定义不能标示为static. 不像普通数据成员,static成员不是通过类构造函数进行初始化,也不能在类的声明中初始化,而是应该在定义时进行初始化.保证对象正好定义一次的最好办法,就是将static 数据成员的定义放在包含类非内联成员函数定义的文件中。
静态数据成员初始化的格式为:
<数据类型><类名>::<静态数据成员名>=<值>
类的静态数据成员有两种访问形式:
<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
static成员变量定义放在cpp文件中,不能放在初始化列表中。Const static成员可就地初始化。
二十三.一个父类写了一个 virtual 函数,如果子类覆盖它的函数不加 virtual ,也能实现多态?在子类的空间里,有没有父类的这个函数,或者父类的私有变量? (华为笔试题)
只要基类在定义成员函数时已经声明了 virtue关键字,在派生类实现的时候覆盖该函数时,virtue关键字可加可不加,不影响多态的实现。子类的空间里有父类的所有变量(static除外)。

二十四.完成字符串拷贝可以使用 sprintf、strcpy 及 memcpy 函数,这些函数有什么区别?

些函数的区别在于 实现功能以及操作对象不同。
(1)strcpy 函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能。
(2)sprintf 函数操作的对象不限于字符串:虽然目的对象是字符串,但是源对象可以是字符串、也可以是任意基本类型的数据。这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能。如果源对象是字符串,并且指定 %s 格式符,也可实现字符串拷贝功能。
(3)memcpy 函数顾名思义就是内存拷贝,实现将一个内存块的内容复制到另一个内存块这一功能。内存块由其首地址以及长度确定。程序中出现的实体对象,不论是什么类型,其最终表现就是在内存中占据一席之地(一个内存区间或块)。因此,memcpy 的操作对象不局限于某一类数据类型,或者说可适用于任意数据类型,只要能给出对象的起始地址和内存长度信息、并且对象具有可操作性即可。鉴于memcpy 函数等长拷贝的特点以及数据类型代表的物理意义,memcpy 函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝。
对于字符串拷贝来说,用上述三个函数都可以实现,但是其实现的效率和使用的方便程度不同:
• strcpy 无疑是最合适的选择:效率高且调用方便。
• sprintf 要额外指定格式符并且进行格式转化,麻烦且效率不高。
• memcpy 虽然高效,但是需要额外提供拷贝的内存长度这一参数,易错且使用不便;并且如果长度指定过大的话(最优长度是源字符串长度 + 1),还会带来性能的下降。其实 strcpy 函数一般是在内部调用 memcpy 函数或者用汇编直接实现的,以达到高效的目的。因此,使用 memcpy 和 strcpy 拷贝字符串在性能上应该没有什么大的差别。
对于非字符串类型的数据的复制来说,strcpy 和 snprintf 一般就无能为力了,可是对 memcpy 却没有什么影响。但是,对于基本数据类型来说,尽管可以用 memcpy 进行拷贝,由于有赋值运算符可以方便且高效地进行同种或兼容类型的数据之间的拷贝,所以这种情况下 memcpy 几乎不被使用 。memcpy 的长处是用来实现(通常是内部实现居多)对结构或者数组的拷贝,其目的是或者高效,或者使用方便,甚或两者兼有。

二十五.应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?

对于一个进程的内存空间而言,可以在逻辑上分成 3个部份:代码区,静态数据区和动态数据区。
动态数据区一般就是“堆栈”。 栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的“栈”。
全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。

二十六.C++函数中值的传递方式有哪几种?

值传递、指针传递和引用传递。

二十七. C++里面是不是所有的动作都是main()引起的?

全局变量的初始化,就不是由main函数引起的

二十八.static关键字:

static关键字的主要作用是使其被声明的对象表示为静态的,存储在内存的静态存储区。只是它修饰的对象,位置不一样,其意义也就不一样。

1.若在函数体内声明局部变量为static,则这个局部变量内存只分配一次,在下次调用时还维持上一次的值。

2.在模块内定义的static全局变量或者函数只能被本模块的函数访问,不能被其他模块所访问。

3.在类中定义的static成员变量或者成员函数只能被这个类所访问。类的static成员变量只有一份复制。类的static成员函数不接受this指针,而且只能访问static成员变量。

二十九.const关键字:

const关键字的作用是阻止被修饰的对象修改,在不同位置,不同的修饰对象,其意义也就不一样。

1.对于变量来说,声明const之后就不能改变其值了,而且在声明是必须初始化。

2.对指针来说,可以限制指针所指向的内存不可修改,这时const位于*之前,也可以限制指针本身不可修改,这时const位于*之后。也可以在*前后都加const,表示指针本身和其所指向的内容都不可修改。

3.作为函数的形参,表示传入的参数不可修改,一般配合引用和指针一起使用。

4.对于类的成员函数,表示这是个const成员函数,不能修改类的成员变量。

5.若函数的返回值为const,表示其返回值不可修改,不能作为"左值"。

三十.易误解:如果int a[5], 那么a与&a是等价的,因为两者地址相同。

一定要注意a与&a是不一样的,虽然两者地址相同,但意义不一样,&a是整个数组对象的首地址,而a是数组首地址,也就是a[0]的地址,a的类型是int[5],a[0]的类型是int,因此&a+1相当于a的地址值加上sizeof(int) * 5,也就是a[5],下一个对象的地址,已经越界了,而a+1相当于a的地址加上sizeof(int),即a[1]的地址。

三十一.如何将一个小数分解成整数部分和小数部分?

使用MATH.H中的double modf(double num, double *i);函数

	double number = 123.54;
	double fraction;
	double integer;
	fraction = modf(number, &integer);
integer = 123. fraction = 0.54
三十二.可作为函数重载判断依据的有:参数个数、参数类型、const修饰符;不可以作为重载判断依据的有:返回类型。

三十三:

int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = &(a + 1)[3];
printf("%d\n", *p);
输出:5 说明:因为a+1指向a的第二个元素,[3]表示再向后移动3个元素。
三十四:


char str1[] = "abc";
 char str2[] = "abc";
 const char str3[] = "abc";
 const char str4[] = "abc";
 const char *str5 = "abc";
 const char *str6 = "abc";
 char *str7 = "abc";
 char *str8 = "abc";
 cout << (str1 == str2) << endl;
 cout << (str3 == str4) << endl;
 cout << (str5 == str6) << endl;
 cout << (str7 == str8) << endl;
输出:0 0 1 1

数组对象要么在栈上创建空间,要么在全局变量去创建空间,此处实在栈上创建的,每个对象都有独立存贮空间,虽然它们的内容都一样。而这里的指针变量是在常量存储区保存着,因为它们的内容都一样,所以地址也一样。

三十五:C的结构体和C++结构体的区别

(1)C的结构体内不允许有函数存在,C++允许有内部成员函数,且允许该函数是虚函数。所以C的结构体是没有构造函数、析构函数、和this指针的。
(2)C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。
(3)C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的。

三十六:如何在类中定义常量成员并为其初始化?

只能在构造函数的初始化列表里对const成员初始化,像下面这样:

class CBook {
public:
    const double m_price;
    CBook() :m_price(8.8) { }
};
下面的做法是错误的:
class CBook {
public:
    const double m_price;
    CBook() {
        m_price = 8.8;
    }
};
而下面的做法虽未报错,但有个warning,也不推荐:
class CBook {
public:
    const double m_price = 8.8; // 注意这里若没有const则编译出错
    CBook() { }
};
三十七:在定义类的成员函数时使用mutable关键字的作用是什么?

当需要在const方法中修改对象的数据成员时,可以在数据成员前使用mutable关键字,防止出现编译出错。例子如下:

class CBook {
public:
    mutable double m_price; // 如果不加就会出错
    CBook(double price) :m_price(price) { }
    double getPrice() const; // 定义const方法
};
double CBook::getPrice() const {
    m_price = 9.8;
    return m_price;
}

三十八.构造函数、拷贝构造函数、析构函数的调用点和顺序问题

class CBook{
public:
	CBook()
	{
		std::cout<<"Constructor"<<std::endl;
	}
	~CBook()
	{
		std::cout<<"Destructor"<<std::endl;
	}
	CBook(const CBook& obj) //拷贝构造函数
	{
		std::cout<<"Copy Constructor"<<std::endl;
	}
	CBook& operator=(const CBook& obj) //拷贝赋值函数
	{	
		 std::cout<<"operator ="<<std::endl;
		 return *this;
	}
};

void Invoke(CBook b)
{
	std::cout<<"Invoke"<<std::endl;
}

void main()
{
	CBook b; //构造函数
	Invoke(b); //拷贝构造函数
	CBook c = b; //拷贝构造函数,声明时同时初始化,所以调用拷贝构造函数
	CBook d;
	d = c; //拷贝赋值函数,先申明,再赋值,拷贝赋值函数
	system("pause");
}

/*输出:
CBook b的									Constructor  
Invoke(b)先调用								Copy Constructor
Invoke(b)的									Invoke
释放Invoke(b)值传递时创建的临时对象		Destructor
CBook c = b的								Copy Constructor
CBook d的									Constructor
d = c的										operator =
*/

/*总结:
以下三个场景将调用拷贝构造函数:
1.当函数参数为类的对象且是值传递的时候。
2.将类的对象作为函数的返回值。
3.声明类的对象的同时使用=号操作符将其赋值给另外一个对象。(若是先声明了,再在另外一条语句中赋值给另外一个对象,则调用拷贝赋值语句)

一般编译器会自动生成构造函数,析构函数,拷贝构造函数,拷贝赋值语句。若类中有指针的成员变量,最好重写
拷贝构造和拷贝复制语句,完成深拷贝的动作。默认的是浅拷贝,既:只拷贝对象的值,若是指针变量,只拷贝指针的地址,
而指针所指向内存中的内容不会被拷贝,这样容易产生问题。
拷贝构造函数是有唯一一个的本类型的const对象的引用的参数。
拷贝复制函数参数同拷贝构造函数一样,不过是重载了=操作符,并返回一个本类型的一个引用对象。
若调用了拷贝构造函数且不是使用=号的场景下肯定还会再接着调用一次析构函数,因为还要释放这个调用拷贝构造函数的语句所创建临时对象。
*/

三十九.C++中的explicit关键字有何作用?

禁止将构造函数作为转换函数,即禁止构造函数自动进行隐式类型转换。例如CBook中只有一个参数m_price,在构建对象时可以使用CBook c = 9.8这样的隐式转换,使用explicit防止这种转换发生。

四十.在C++中,如果确定了类的对象的构造函数的创建过程,在该构造函数中如果调用了其它重载的构造函数,它将不会执行其它构造函数的初始化列表部分代码,而是执行函数体代码,此时已经退化成普通函数了。

class CBook {
public:
    double m_price;
    CBook() {
        CBook(8.8);
    }
    CBook(double price) : m_price(price) { }
};
int main() {
    CBook c;
    cout << c.m_price << endl; // 此时并不会输出理想中的8.8
}
四十一.静态数据成员只能在全局区域进行初始化,而不能在类体中进行(构造函数中初始化也不行),且静态数据成员不涉及对象,因此不受类访问限定符的限制。
class CBook {
public:
    static double m_price;
};
double CBook::m_price = 8.8; // 只能在这初始化,不能在CBook的构造函数或直接初始化
四十二.重载++和–时是怎么区分前缀++和后缀++的?
当编译器看到++a(先自增)时,它就调用operator++(a);
但当编译器看到a++时,它就调用operator++(a, int)。即编译器通过调用不同的函数区别这两种形式。

四十三.C++的多态性分为静态多态和动态多态

静态多态性:编译期间确定具体执行哪一项操作,主要是通过函数重载和运算符重载来实现的;
动态多态性:运行时确定具体执行哪一项操作,主要是通过虚函数来实现的。

四十四.虚函数原理考点

class A {
public:
    virtual void funa();
    virtual void funb();
    void func();
    static void fund();
    static int si;
private:
    int i;
    char c;
	char d;
	char e;
	char f;
	char g;
};

void main()
{
	std::cout<<sizeof(A)<<std::endl;
	system("pause");
}
问:sizeof(A) = ?(16)

解答:
关于类占用的内存空间,有以下几点需要注意:
(1)如果类中含有虚函数,则编译器需要为类构建虚函数表,类中需要存储一个指针指向这个虚函数表的首地址,注意不管有几个虚函数,都只建立一张表,所有的虚函数地址都存在这张表里,类中只需要一个指针指向虚函数表首地址即可。
(2)类中的静态成员是被类所有实例所共享的,它不计入sizeof计算的空间
(3)类中的普通函数或静态普通函数都存储在栈中,不计入sizeof计算的空间
(4)类成员采用字节对齐的方式分配空间

所以虽然有2个虚函数,但只有一个虚函数表,由一个指针管理,这个指针占4个字节。

    void func(); static void fund(); static int si;这几个不算空间。
    int i;占4个字节。

    char c; char d; char e; char f; char g;共占5个字节,但因为内存是以4字节对齐的,所以应该是4的倍数,应该占8个字节

总计16个字节。

但是如果是空类,则它只占一个字节,编译器会使用一个char标记其位置。

四十五.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern "C"

C++语言支持函数重载,C 语言不支持函数重载。函数被C++编译后在库中的名字与C 语言的不同。假设某个函数的原型为: void foo(int x, int y);
该函数被C 编译器编译后在库中的名字为_foo , 而C++ 编译器则会产生像_foo_int_int 之类的名字。C++提供了C 连接交换指定符号extern“C”来解决名字匹配问题。

四十六.一个类有基类、内部有一个其他类的成员对象,构造函数的执行顺序是怎样的
先执行基类的(如果基类当中有虚基类,要先执行虚基类的,其他基类则按照声明派生类时的顺序依次执行),再执行成员对象的,最后执行自己的。

四十七.C++是不是类型安全的?
不是。两个不同类型的指针之间可以强制转换。C#是类型安全的。

四十九.Struct 和class 的区别
struct 中成员变量和成员函数默认访问权限是public,class 是private

五十.写出BOOL,int,float,指针类型的变量a 与零的比较语句。

BOOL : if ( !a )
int : if ( a == 0)
float : const EXPRESSION EXP = 0.000001
if ( a < EXP && a >-EXP)
pointer : if ( a != NULL)
五十一.const 与#define 相比优点
(1) const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。
(2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

五十二.类成员函数的重载、覆盖和隐藏区别
成员函数被重载的特征:
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

覆盖是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)
五十三.野指针

野指针”是很危险的,if语句对它不起作用。“野指针”的成因主要有三种:
(1)指针变量没有被初始化。指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
char *p = NULL; char *str = (char *) malloc(100);
(2)指针p被free或者delete之后,没有置为NULL
(3)指针操作超越了变量的作用范围。所指向的内存值对象生命期已经被销毁
五十四.const常量与define宏定义的区别?
(1)编译器处理方式不同。define宏是在预处理阶段展开,生命周期止于编译期。只是一个常数、一个命令中的参数,没有实际的存在。#define常量存在于程序的代码段。const常量是编译运行阶段使用,const常量存在于程序的数据段.
(2)类型和安全检查不同。define宏没有类型,不做任何类型检查,仅仅是展开。const常量有具体的类型,在编译阶段会执行类型检查。
(3) 存储方式不同。define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。const常量会在内存中分配(可以是堆中也可以是栈中)
五十五.堆和栈的区别?
1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。由系统自动分配。声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间 。只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域,栈的大小是2M。如果申请的空间超过栈的剩余空间时,将提示overflow。栈由系统自动分配,速度较快。但程序员是无法控制的。函数调用时,第一个进栈的是主函数中后的下一条指令,的地址,然后是函数的各个参数。在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
2、堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,需要程序员自己申请,并指明大小,在c中malloc函数,在C++中用new运算符。首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。堆是向高地址扩展的数据结构,是不连续的内存区域。而链表的遍历方向是由低地址向高地址。
堆的大小受限于计算机系统中有效的虚拟内存。堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便一般是在堆的头部用一个字节存放堆的大小。
五十六.类和结构体的区别
结构体在默认情况下的成员都是public的,而类在默认情况下的成员是private的。结构体和类都必须使用new创建,struct保证成员按照声明顺序在内存在存储,而类不保证。
五十七.C++四种强制类型转换

(1)const_cast 

去const属性,去掉类型的const或volatile属性。

struct SA{ int k};
const SA ra;
ra.k = 10; //直接修改const类型,编译错误 

SA& rb = const_cast<SA&>(ra); rb.k = 10; //可以修改
(2)static_cast
主要用于基本类型之间和具有继承关系的类型之间的转换
。用于指针类型的转换没有太大的意义。static_cast是无条件和静态类型转换,可用于基类和子类的转换,基本类型转换,把空指针转换为目标类型的空指针,把任何类型的表达式转换成void类型,static_cast不能进行无关类型(如非基类和子类)指针之间的转换。
int a; double d = static_cast<double>(a); //基本类型转换
int &pn = &a; void *p = static_cast<void*>(pn); //任意类型转换为void
(3)dynamic_cast
可以用它把一个指向基类的指针或引用对象转换成继承类的对象,动态类型转换,运行时类型安全检查(转换失败返回NULL)。基类必须有虚函数,保持多态特性才能用dynamic_cast。只能在继承类对象的指针之间或引用之间进行类型转换

class BaseClass{public: int m_iNum; virtual void foo(){};};
class DerivedClass:BaseClass{public: char* szName[100]; void bar(){};};
BaseClass* pb = new DerivedClass();
DerivedClass *p2 = dynamic_cast<DerivedClass *>(pb);
BaseClass* pParent = dynamic_cast<BaseClass*>(p2);
//子类->父类,动态类型转换,正确
(4)reinterpreter_cast
转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。主要是将一个类型的指针,转换为另一个类型的指针,不同类型的指针类型转换用reinterpreter_cast,最普通的用途就是在函数指针类型之间进行转换
int DoSomething(){return 0;};
typedef void(*FuncPtr)(){};
FuncPtr funcPtrArray[10];
funcPtrArray[0] = reinterpreter_cast<FuncPtr>(&DoSomething);
五十八.多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?

虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.而对象的隐藏成员--虚拟函数表指针是在运行期也就是构造函数被调用时进行初始化的,这是实现多态的关键.

五十九.C++中哪些函数不能被声明为虚函数?
普通函数(非成员函数),构造函数,内联成员函数、静态成员函数、友元函数。
(1)虚函数用于基类和派生类,普通函数所以不能
(2)构造函数不能是因为虚函数采用的是虚调用的方法,允许在只知道部分信息的情况的工作机制,特别允许调用只知道接口而不知道对象的准确类型的方法,但是调用构造函数即使要创建一个对象,那势必要知道对象的准确类型。
(3)内联成员函数的实质是在调用的地方直接将代码扩展开
(4)继承时,静态成员函数是不能被继承的,它只属于一个类,因为也不存在动态联编等
(5)友元函数不是类的成员函数,因此也不能被继承
六十.数组int c[3][3];

c,*c,**c,代表什么意思?

c是第一个元素的地址,*c是第一行元素的首地址,其实第一行元素的地址就是第一个元素的地址,**c是提领第一个元素。

为什么c,*c的值相等?

c: 数组名;是一个二维指针,它的值就是数组的首地址,也即第一行元素的首地址(等于 *c),也等于第一行第一个元素的地址( & c[0][0]);可以说成是二维数组的行指针。
*c: 第一行元素的首地址;是一个一维指针,可以说成是二维数组的列指针。
**c:二维数组中的第一个元素的值;即:c[0][0]
所以:c 和 *c的值是相等的,但他们两者不能相互赋值,(类型不同)

(c+1),(*c+1)的值不等
(c + 1) :c是行指针,(c + 1)是在c的基础上加上二维数组一行的地址长度,即从&c[0][0]变到了&c[1][0];
(*c + 1):*c是列指针,(*c + 1)是在*c的基础上加上二数组一个元素的所占的长度,&c[0][0]变到了&c[0][1],从而(c + 1)和(*c + 1)的值就不相等了。

六十一.int **pa[4][3],则变量pa占有的内存空间是多少?
nt **p,在32位机器上 sizeof(p) = 4;总共占有4*3*sizeof(p) = 48.
六十二.class MyStruct{ double ddal; char dda; int type;}大小,结构体对齐

在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)为16。
其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了“对齐”处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的始地址偏移量必须为该变量的类型占用字节数的倍数,如Char偏移量为sizeof(char)即1的倍数。
先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同,偏移量0刚好为sizeof(double)的倍数,该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,占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)的倍数,所以没有空缺的字节需要填充。

六十三.在main函数执行之前,还会执行什么代码和工作
运行全局构造器,全局对象的构造函数会在main函数之前执行,设置栈指针,初始化static静态和global全局变量,即数据段的内容,将未初始化部分的赋初值:数值型short,int,long等为0,bool为FALSE,指针为NULL等,将main函数的参数,argc,argv等传递给main函数
六十四.如何判断一段程序是由C 编译程序还是由C++ 编译程序编译的?
C++ 编译时定义了 __cplusplus,C 编译时定义了 _STDC_
六十五.关键字volatile有什么含意?并给出三个不同的例子
一个定义为volatile的变量是说这变量可能会被意想不到地改变,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1) 并行设备的硬件寄存器(如:状态寄存器)
2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3) 多线程应用中被几个任务共享的变量
深究:一个参数既可以是const还可以是volatile,一个例子是只读的状态寄存器,它是volatile因为它可能被意想不到地改变,是const因为程序不应该试图去修改它。一个指针可以是volatile,一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
六十六.动态连接库的两种方式?

调用一个DLL中的函数有两种方法:
1.载入时动态链接(load-time dynamic linking),模块非常明确调用某个导出函数,使得他们就像本地函数一样。这需要链接时链接那些函数所在DLL的导入库,导入库向系统提供了载入DLL时所需的信息及DLL函数定位。
2.运行时动态链接(run-time dynamic linking),运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的出口地址,然后就可以通过返回的函数指针调用DLL函数了。如此即可避免导入库文件了。
六十七.简单描述Windows内存管理的方法。
程序运行时需要从内存中读出这段程序的代码,代码的位置必须在物理内存中才能被运行,由于现在的操作系统中有非常多的程序运行着,内存中不能够完全放下,所以引出了虚拟内存的概念。把哪些不常用的程序片断就放入虚拟内存,当需要用到它的时候在load入主存(物理内存)中。内存管理也计算程序片段在主存中的物理位置,以便CPU调度。

内存管理有块式管理,页式管理,段式和段页式管理。现在常用段页式管理
块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程 序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但时易于管理。
页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多
段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上,计算机最耗时间的大家都知道是I/O吧
段页式管理:结合了段式管理和页式管理的优点。把主存分为若干页,每一页又分为若干段,好处就很明显

六十八.什么是函数模板

函数模板技术定义了参数化的非成员函数,使得程序能够使用不同的参数类型调用相同的函数,而至于是何种类型,则是由编译器确定从模板中生成相应类型的代码。编译器确定了模板函数的实际类型参数,称之为模板的实例化。
template<class T>定义模板标识
T Add(T a, T b) //函数模板
{
T result = a + b;
return a + b; //将两个参数使用“+”运算符进行运算,这两个参数并不知道是何种类型
}

#include<iostream>  //包含标准输入输出头文件
#include<string>   //C++中的字符串处理头文件
using namespace std;
template<class T>
T Add(T a, T b)         //函数模板
{
   T result = a + b;
   return a + b;    //将两个参数使用“+”运算符进行运算,这两个参数并不知道是何种类型
}
int main(int argc, char* argv[])
{
    cout<<"2+3="<<Add(2,3)<<endl;  //输出整形的+运算结果
   cout<<"sdf+123="<<Add(string("sdf"), string("123"))<<endl;
  return 0;
}
六十九.什么是类模板
描述了能够管理其他数据类型的通用数据类型,通常用于建立包含其他类型的容器类,对于这些容器,无论是哪一种数据类型,其操作方式是一样的,但是针对具体的类型又是专用的,
template<class T>
class TemplateSample
{
private:
T& emtity; //使用参数类型成员
public:
void F(T& arg); //使用参数类型定义成员函数
}

TemplateSample<int , char, 12>demo;      //使用非类类型的模板
#include<iostream>
template<class T, class T2, int num>
class CSampleTemplate
{
   private:
   T t1;
   T2 t2;
  public:
  CSampleTemplate(T arg1, T2 arg2)         //构造函数中使用模板参数
  {
      t1 = arg1 + num;
      t2 = arg2 + num;
  }
  void Write()
 {
  std::cout<<"t1:"<<t1<<"t2"<<t2<<endl;
 }
 
CSampleTemplate ()
{}
}
int main(int argc, char* argv[])
{
    CSampleTemplate<int, int, 3>temp(1,2);
    temp.Write();
    return 0;
}
七十.什么是容器
STL是一个标准的C++库,容器只是其中一个重要的组成部分,有顺序容器和关联容器
1)顺序容器,指的是一组具有相同类型T的对象,以严格的线性形式组织在一起,包括vector<T>, deque<T>, list<T>
2)关联容器,提供一个key实现对对象的随机访问,其特点是key是有序的元素是按照预定义的键顺序插入的,
set<Key> ,集合, 支持唯一键值,提供对键本身的快速检索,例如set<long>:{学号}
set<Key>,多重集合,支持可重复键值,提供对键本身的快速检索,例如multiset<string>:{姓名}
map<Key, T>,支持唯一Key类型的键值,提供对另一个基于键的类型的快速检索,例如map<long,string>:{学号,姓名}
multimap<Key, T>, 多重映射,支持可重复Key值,提供对另外一个基于键类型T的快速检索,例如map<string, string>:{姓名,地址}

七十一.引用和多态的关系
1.基类的引用可以指向派生类的实例。

2.基类的指针可以指向派生类实例的地址。
七十二.结构与联合有和区别?
1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。
2. 对于联合的不同成员赋值, 将会对其它成员重写, 原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。
七十三.编写类String的构造函数、析构函数和赋值函数。

class String
{ 
 public: 
  String(constchar*str = NULL); // 普通构造函数 
  String(const String &other); // 拷贝构造函数 
  ~ String(void); // 析构函数 
  String & operator =(const String &other); // 赋值函数 
 private: 
  char*m_data; // 用于保存字符串 
};
  解答:
//普通构造函数
String::String(constchar*str) 
{
 if(str==NULL) 
 {
  m_data =newchar[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空
  //加分点:对m_data加NULL 判断
  *m_data ='\0'; 
 } 
 else
 {
  int length = strlen(str); 
  m_data =newchar[length+1]; // 若能加 NULL 判断则更好 
  strcpy(m_data, str); 
 }
}
// String的析构函数
String::~String(void) 
{
 delete [] m_data; // 或deletem_data;
}
//拷贝构造函数
String::String(const String &other)    // 得分点:输入参数为const型
{ 
 int length = strlen(other.m_data); 
 m_data =newchar[length+1];     //加分点:对m_data加NULL 判断
 strcpy(m_data, other.m_data); 
}
//赋值函数
String & String::operator =(const String &other) // 得分点:输入参数为const型
{ 
 if(this==&other)   //得分点:检查自赋值
  return*this; 
 delete [] m_data;     //得分点:释放原有的内存资源
 int length = strlen( other.m_data ); 
 m_data =newchar[length+1];  //加分点:对m_data加NULL 判断
 strcpy( m_data, other.m_data ); 
 return*this;         //得分点:返回本对象的引用
}

七十四.RTTI

RTTI(Run-Time Type Information),通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。
在C++中存在虚函数,也就存在了多态性,对于多态性的对象,在程序编译时可能会出现无法确定对象的类型的情况。当类中含有虚函数时,其基类的指针就可以指向任何派生类的对象,这时就有可能不知道基类指针到底指向的是哪个对象的情况,类型的确定要在运行时利用运行时类型标识做出。为了获得一个对象的类型可以使用typeid函数,该函数反回一个对type_info类对象的引用,要使用typeid必须使用头文件<typeinfo>

该函数的主要作用就是让用户知道当前的变量是什么类型的,比如使用typeid(a).name()就能知道变量a是什么类型的。如:

	CBook a;
	std::cout<<typeid(a).name()<<std::endl;
将输出class CBook 
使用type_info类中的name()成员函数反回对象的类型的名称。其方法为:typeid(object).name()其中object是要显示其相应类型名的对象,该函数反回的名字因编译器而定。这里要注意的就是使用方式一中提到的虚函数类型的问题,即如果有类A,且有虚函数,类B,C,D都是从类A派生的,且都重定义了类A中的虚函数,这时有类A的指针p,再把对象类B的对象的地址赋给指针p,则typeid(p).name()将反回的类型将是A*,因为这里的p表示的是一个指针,该指针是类型为A的指针,所以返回A*,而typeid(*p).name()将返回B,因为指针p是指向类B的对象的,而*p就表示的是类B的对象,所以返回B。

使用type_info类中重载的= =与!=比较两个对象的类型是否相等。使用该方法需要调用类type_info中重载的= =和!=操作符,其使用方法为typid(object1)= =typid(object2);如果两个对象的类型相等则返回1,如果不相等则为0。这种使用方法通常用于比较两个带有虚函数的类的对象是否相等,比如有类A,其中定义有虚函数,而类B,类C,类D,都是从类A派生而来的且重定义了该虚函数,这时有两个类A的指针p和p1,按照虚函数的原理,基类的指针可以指向任何派生类的对象,在这时就有可能需要比较两个指针是否指向同一个对象,这时就可以这样使用typeid了,typeid(*p)= =typeid(*p1);这里要注意的是typeid(*p)与typeid(p)是指的不同的对象类型,typeid(p)表示的是p的类型,在这里p是一个指针,这个指针指向的是类A的对象,所以p的类型是A*,而typeid(*p)则不一样,*p表示的是指针p实际所指的对象的类型,比如这里的指针p指向派生类B,则typeid(*p)的类型为B。所以在测试两个指针的类型是否是相等时应使用*p,即typeid(*p)= =typeid(*p1)。如果是typeid(p)= =typeid(p1)的话,则无论指针p和p1指向的什么派生类对象,他们都是相等的,因为都是A *的类型。

七十五.<<,>>运算符

左移一位相当于乘以2,右移一位相当于除以2

一,问:计算表达式14 << 2的值。
答:表达式14 << 2的值为56,因为14(即二进制的00001110)向左移两位等于56(即二进制的 00111000)。你可以这样理解,左移一位相当于乘以2,左移两位就是乘以4,即14*4 = 56。
二,问: 计算表达式8 >> 2的值。
答:表达式8 >> 2的值为2,因为8(即二进制的00搜索001000)向右移两位等于2(即二进制的00000010)。 同样,右移一位相当于除以2,右移两位相当于除以4,即 8/4 = 2。

七十六.下面关于“联合”的题目的输出?

#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)
七十七.已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。

/*
编写strcpy函数(10分)
已知strcpy函数的原型是
    char *strcpy(char *strDest, const char *strSrc);
    其中strDest是目的字符串,strSrc是源字符串。
(1)不调用C++/C的字符串库函数,请编写函数 strcpy
(2)strcpy能把strSrc的内容复制到strDest,为什么还要char * 类型的返回值?
答:为了 实现链式表达式。                            // 2分
例如    int length = strlen( strcpy( strDest, “hello world”) );
*/
#include <assert.h>
#include <stdio.h>
char*strcpy(char*strDest, constchar*strSrc)
{
    assert((strDest!=NULL) && (strSrc !=NULL));        // 2分
char* address = strDest;                          // 2分
while( (*strDest++=*strSrc++) !='\0' )       // 2分
       NULL; 
    return address ;                                   // 2分
}
#include<stdio.h>
#include<assert.h> 
int strlen( constchar*str )  // 输入参数const
{
    assert( str != NULL );  // 断言字符串地址非0
int len = 0;
    while( (*str++) !='\0' ) 
    { 
        len++; 
    } 
    return len;
}
七十八.在C++程序中调用被C 编译器编译后的函数,为什么要加extern “C”?
首先,作为extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数
extern "C"是连接申明(linkage declaration),被extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看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
#include "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++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" { }。

在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern"C"函数声明为extern类型。

七十九.多态的作用?

主要是两个:
1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;
2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

八十.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 的支持。

八十一.有哪几种情况只能用intializationlist 而不能用assignment?

当类中含有const、reference 成员变量;基类的构造函数都需要初始化表。

八十二.描述内存分配方式以及它们的区别?

1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
4) 代码区。

八十三. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的?

#ifdef __cplusplus
  cout<<"c++";
#else
  cout<<"c";
#endif
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值