本文共 11683 字,大约阅读时间需要 38 分钟。
冯诺依曼体系结构用于存储程序方式,指令和数据不加区别混合存储在同一存储器中。有如下特点:
请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1
int check_CPU(){ union w{ int a; char b; }c; c.a = 1; return c.b == 1;}
volatile关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器位置的因素更改,比如:操作系统,硬件,或其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码不再进行优化,从而可以提供对特殊地址的稳定访问;
当要求使用volatile声明的变量的值的时候,系统总是重新从它所在的内存读取数据,即使它前面的指令刚刚从该处读取过数据,而且读取的数据立刻被保存;
volatile指出i是随时可能发生改变的,每次使用它的时候必须从i地址读取,因而编译器生成的汇编代码会重新从i的地址读取数据放在b中,而优化的做法是,由于编译器发现两次从i读数据的代码之间没有对i进行过操作,它会自动把上次读的数据放在b中,而不是从重新从i里面读。这样一来,如果i是一个寄存器变量或者表示一个端口数据就容易出错,所以说volatile可以保证对特殊地址的稳定访问;
static:
const:
const classA operator*(const classA& a1,const classA& a2);
operator* 的返回结果必须是一个const 对象,如果不是,这样的变态代码也不会编译出错:classA a,b,c; (a * b) = c; // 对a*b的结果赋值
操作(a * b) = c显然不符合编程者的初衷,也没有任何意义;编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:
class String{private: char* m_data; //用于保存字符串public: String(const char* str = NULL); //普通构造函数 String(const String& other); //拷贝构造函数 ~String(); String& operator=(const String& other); //赋值函数};String::String(const char* str){ if (str == NULL){ m_data = new char[1]; //对空字符串自动申请存放结束标志'\0'的空 *m_data = '\0'; } else{ int len = strlen(str); m_data = new char[len + 1]; strcpy(m_data, str); }}String::String(const String& other){ int len = strlen(other.m_data); m_data = new char[len + 1]; strcpy(m_data, other.m_data);}String::~String(){ delete[] m_data;}String& String::operator=(const String& other){ if (this == &other)return *this; delete[] m_data; int len = strlen(other.m_data); m_data = new char[len + 1]; strcpy(m_data, other.m_data); return *this;}
写出float与“零值”比较的if语句:
const float EPSINON = 0.00001;if((x >= -EPSINON) || (x <= EPSINON))
浮点型变量不精确,所以不可将float变量用“==”或“!=”与数字比较,应该设法转化为“>=”或“<=”形式;
void* memcpy(void* dst, const void* src, size_t len){ if (!dst || !src)return NULL; void* ret = dst; if (dst <= src || (char*)dst >= (char*)src + len){ //没有内存重叠,从低地址开始复制 while (len--){ *(char*)dst = *(char*)src; dst = (char*)dst + 1; src = (char*)src + 1; } } else{ //存在内存重叠,从高地址开始复制 src = (char*)src + len - 1; dst = (char*)dst + len - 1; while (len--){ *(char*)dst = *(char*)src; dst = (char*)dst - 1; src = (char*)src - 1; } } return ret;}
给定N张扑克牌和一个随机函数,设计一个洗牌算法
void shuffle(int cards[], int n){ if (cards == NULL)return; srand(time(0)); for (int i = 0; i < n - 1; i++){ //保证每次第i位的值不会涉及到第i为以前 int index = i + rand() % (n - i); swap(cards[i], cards[index]); }}
100亿个整数,内存足够,如何找到中位数?内存不足,如何找到中位数?
智能指针是一种资源管理类,通过对原始指针进行封装,在资源管理对象进行析构时对指针指向的内存进行释放,通常使用引用计数方式进行管理:
templateclass SmartPointer{private: T* ptr; size_t* reference_count; void releaseCount(){ if (ptr){ (*reference_count)--; if ((*reference_count) == 0){ delete ptr; delete reference_count; } } }public: SmartPointer(T* p = NULL) :ptr(p), reference_count(new size_t){ if (p)*reference_count = 1; else *reference_count = 0; } SmartPointer(const SmartPointer& src){ if (ptr != src.ptr){ ptr = src.ptr; reference_count = src.reference_count; (*reference_count)++; } } SmartPointer& operator=(const SmartPointer& src){ if (ptr == src.ptr)return *this; releaseCount(); ptr = src.ptr; reference_count = src.reference_count; (*reference_count)++; return *this; } T* operator*(){ return (*ptr); } T* operator->(){ return ptr; } ~SmartPointer(){ if ((--(*reference_count)) == 0){ delete ptr; delete reference_count; } } size_t get_reference(){ return *reference_count; }};
实现单例模式,要求线程安全;
#include#include using namespace std;class Lock{private: CCriticalSection m_cs;public: Lock(CCriticalSection cs) :m_cs(cs){ m_cs.Lock(); } ~Lock(){ m_cs.Unlock(); }};class Singleton{private: Singleton(); Singleton(const Singleton&); Singleton& operator=(const Singleton&);public: static Singleton *Instantialize(); static Singleton *pInstance; static CCriticalSection cs;};Singleton* Singleton::pInstance = NULL;Singleton* Singleton::Instantialize(){ if (pInstance == NULL){ //double check Lock lock(cs); //用lock实现线程安全,用资源管理类,实现异常安全 //使用资源管理类,在抛出异常的时候,资源管理类对象被析构,析构总是发生无论是因为异常抛出还是语句块结束; if (pInstance == NULL){ pInstance = new Singleton(); } } return pInstance;}
const可以节省空间,避免不必要的内存分配;
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干份拷贝;
表达式形式的宏定义一例:
#define ExpressionName(Var1,Var2) (Var1+Var2)*(Var1-Var2) 这种宏形式作用与函数类似,但它使用预处理器,没有堆栈,使用上比函数高效。但它只是预处理器上符号表的简单替换,不进行函数有效性检测以及使用C++的成员访问控制;inline 推出的目的,也正是为了取代这种表达式形式的宏定义,它消除了它的缺点,同时又很好的继承了它的优点,inline代码放入了预编译器符号表中,高效;它是真正的函数, 调用时有严格的参数检测;它也可作为类的成员函数;
直接在class定义中定义各函数成员,系统将它们作为内联函数处理;成员函数是内联函数,意味着:每个对象都有该函数一份独立的拷贝;在类外,如果使用inline定义函数,则系统也会作为内联函数处理;
malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。
因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。
C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。
new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。
new建立的是一个对象,malloc分配的是一块内存;
C++编译器在实现const的成员函数的时候为了确保该函数不能修改类中参数的值,会在函数中添加一个隐式的const this*,但一个成员为static的时候,该函数时没有this指针的,也就是说此时const的用法和static是冲突的;
从对象模型上来说,类的非static成员函数在编译的时候都会扩展加上一个this参数,const的成员函数被要求不能修改this指针所指向的对象,而static函数编译的时候并不扩充加上this参数,自然无所谓const;
C++中虚函数使用虚函数表和虚函数表指针实现,虚函数表是一个类的虚函数的地址表,用于索引类本身以及父类的虚函数的地址,假如子类的虚函数重写了父类的虚函数,则对应的虚函数表会把对应的虚函数替换为子类的虚函数的地址;虚函数表指针存在于每个对象中(出于效率考虑,会放在对象的开始地址处),它指向对象所在类的虚函数表的地址;在多继承环境中,会存在多个虚函数表指针,分别指向对应不同基类的虚函数表;
子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用数据子类的该函数,且这样的函数调用无法在编译期间确认,而是在运行期确认,也叫作延迟绑定;
访问普通函数更快,因为普通函数的地址在编译阶段已经确定,因此在访问时直接调用对应地址的函数,而虚函数在调用时,需要首先在虚函数表中寻找虚函数所在地址,因此相比普通函数速度要慢一些;
若存在类继承关系并且析构函数中需要析构某些资源时,析构函数需要时虚函数,否则当使用父类指针指向子类对象的时候,在delete时只会调用父类的析构函数,而不能调用子类的析构函数,造成内存泄漏等问题;
都不可以:
方法的重写Overriding和重载Overloading是多态性的不同表现。
方法的重写Overriding是父类和子类之间多态性的一种表现,重载Overloading是一个类中多态性的表现。如果子类中定义的方法和其父类有相同的名称和参数,则该方法被重写(Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被“屏蔽”了,而且如果子类的方法名和参数类型和个数都和父类相同,那么子类的返回值类型必须和父类的相同;
如果在一个类中定义了多个同名的方法,它们或有不同的参数个数或有不同的参数类型,则称为方法的重载(Overloading)。Overloading的方法是可以改变返回值的类型。也就是说,重载的返回值类型可以相同也可以不同。
给定一个能够生成0,1两个数的等概率随机数生成器”,如何生成⼀个产生0,1,2,3的等概率随机数生成器?
和上题类似,如何用rand7生成rand9?将两个0,1随机生成器级联,每次产生两个数,则可能产生的结果有(0,0)、(0,1)、(1,0)、(1,1),分别映射到0,1,2,3即可;
两个rand7可以产生49中可能,扔掉后面的4种,保留45种,并平均分成9份,每次产生一个结果时,假如没落在对应区间中就丢掉,否则根据落在那个区间判断是0~8中的哪个?思路:设置三个信号量:S1,S2,S3,由S1 post S2,S2 post S3,S3 post S1,由A线程先开始打印,其他线程必然在等待信号量,所以三个线程一定会按照信号量的顺序来打印;
#include#include #include #include sem_t sem_id1, sem_id2, sem_id3;void* func1(void*);void* func2(void*);void* func3(void*);int main(){ sem_init(&sem_id1, 0, 1); sem_init(&sem_id2, 0, 0); sem_init(&sem_id3, 0, 0); pthread_t pthread_id1, pthread_id2, pthread_id3; pthread_create(&pthread_id1, NULL, func1, NULL); pthread_create(&pthread_id2, NULL, func2, NULL); pthread_create(&pthread_id3, NULL, func3, NULL); pthread_join(pthread_id1, NULL); pthread_join(pthread_id1, NULL); pthread_join(pthread_id1, NULL); return 0;}void* func1(void *){ sem_wait(sem_id1); printf("A\n"); sem_post(sem_id2);}void* func2(void *){ sem_wait(sem_id2); printf("B\n"); sem_post(sem_id3);}void* func3(void *){ sem_wait(sem_id3); printf("B\n"); sem_post(sem_id1);}
目的:最大限定的降低内存的碎片化;
原理:总结:伙伴系统在分配和释放过程中执行互逆的过程,其将会极大力度的抵消碎片的产生;
malloc可以分别由伙伴系统或基于链表的实现:
mmap函数把文件的某一块的内容映射在用户空间上,用户可以直接读写这一块内容。普通读写函数会经历一个内核缓冲过程,多出了数据拷贝的时间。
无论是UNIX I/O还是标准I/O库函数,他们都是通过read,write等底层系统调用来实现的,而read,write都会使用内核进行缓冲;
内核调度并运行进程之前,先检查该进程上是否有未处理的信号,有则发出一个软中断,中断处理函数中执行对应的信号处理函数,因为是通过软中断执行,所以信号处理在自己所有的栈上,不会影响原来的栈;
在网上看到的代码:
int i=1;cout<<
改变一下:
int i=1;cout<< < <
这次结果仍然相同!!
int i=1;int j=2;cout<< < <
结果同上。
int i=1;double j=2;cout<<
根据C99规范,sizeof是一个编译时刻就起作用的运算符,在其内的任何运算都没有意义。
j = sizeof(++i+++i); 在编译的时候被翻译成j = sizeof((++i+++i的数据类型)) 也就是 j = sizeof(int);
只要sizeof的参数表达式没有错误,其结果为从左边数第一个变量的类型的大小值。