2018/12/18 周二
1. C++内存布局分为几个区域,每个区域有什么特点?
主要可以分为 5 个区域,
(1) 栈区:由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
(2) 堆区:由程序员分配释放。
(3) 全局/静态区:全局变量和静态变量的存储是放在一块的,在程序编译时分配。
(4) 文字常量区:存放常量字符串
(5) 程序代码区:存放函数体(类的成员函数,全局函数)的二进制代码
内存布局见CSAPP第七章的图7-13,Linux运行时存储器映像
衍生问题:
1.1 栈和堆的区别
(1) 管理方式不同;(栈是系统自动分配, 堆是需要程序员申请,程序员释放,使用不好可能 memory leak)
(2) 空间大小不同;
栈是向低地址扩展的数据结构,它的容量是系统预先设定好的,一般是1M或者2M?申请的时候不要超过栈的剩余空间。
堆是向高地址扩展的数据结构,链表的方式来存储空闲内存地址的,不连续,链表遍历方向是从低地址向高地址。堆可以获得的空间受限于计算机系统中有效的虚拟内存的大小。(一般来说可以到达 4GB?)
(3) 能否产生碎片不同;(堆是链表分配内存,容易产生碎片,栈不会产生碎片)
对于堆来说,频繁的 new/delete操作势必会造成内存空间的不连续,从而造成大量的碎片,使得程序效率降低。对于栈来说,不会出现这个问题,因为栈是先进先出的。在栈顶弹出之前,永远不可能有一个内存块从栈中间弹出。
(4) 生长方向不同;(栈:高地址->低地址(生长方向向下), 堆: 低地址->高地址(生长方向向上))
(5) 分配方式不同;
内存有两种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配是用 malloc, calloc 函数进行分配,但是栈的动态分配和堆的动态分配是不同的,栈的动态分配由编译器进行释放,不需要手动。
(6) 分配效率不同;(栈:系统分配,速度快,堆:new出来的内存,速度慢)
2.当定义类的时候,编译器会自动为类自动生成那些函数?这些函数各自有什么特点?
构造函数,析构函数
3.什么是浅拷贝,什么是深拷贝?
浅拷贝是说拷贝指向空间的指针,拷贝出来的目标对象的指针和原对象的指针指向内存的同一块空间。(几个对象共用一块内存空间)
深拷贝是说拷贝对象的具体内容,其内容的地址是程序员向系统申请分配的,拷贝结束后拷贝的内容完全一样,但是使用的内存空间地址不同。
4.实现一个自定义的string类,保证main函数的正确执行。(main函数已经给出)
题解:主要是实现深拷贝情况下的构造函数和析构函数。构造函数包含无参构造函数,有参构造函数,拷贝构造函数,和赋值构造函数。析构函数可以delete开辟的堆空间。
1 /* 2 * File: string.cc 3 * Time: 2018-12-18 19:36:01 4 * Author: wyzhang 5 */ 6 7 #include <cstdio> 8 #include <cstring> 9 #include <iostream> 10 11 using std::cout; 12 using std::endl; 13 14 class String { 15 public: 16 String() 17 : _ptr(nullptr) { 18 _ptr = nullptr; 19 cout << __FUNCTION__ << endl; 20 } 21 String(const char * s) 22 : _ptr(new char[strlen(s) + 1]()) { 23 cout << "String(const char * s)" << endl; 24 strcpy(_ptr, s); 25 } 26 27 String & operator = (const char * s) { 28 cout << "String & operator = (const char * s)" << endl; 29 _ptr = new char[strlen(s) + 1](); 30 strcpy(_ptr, s); 31 } 32 33 String & operator = (const String & rhs) { 34 cout << "String & operator = (const String & rhs)" << endl; 35 if (&rhs == this) { 36 return *this; 37 } 38 _ptr = new char[strlen(rhs._ptr)+1](); 39 strcpy(_ptr, rhs._ptr); 40 } 41 String(const String & rhs) 42 : _ptr(new char[strlen(rhs._ptr) + 1]()) { 43 cout << "String(const String & rhs)" << endl; 44 strcpy(_ptr, rhs._ptr); 45 } 46 ~String() { 47 cout << "~String()" << endl; 48 if (_ptr) { 49 delete[] _ptr; 50 } 51 } 52 void print() { 53 if (!_ptr) { 54 cout << "_ptr is nullptr. " << endl; 55 return; 56 } 57 cout << "string = " << _ptr << endl; 58 } 59 private: 60 char * _ptr; 61 }; 62 int main () { 63 String str1; 64 str1.print(); 65 66 String str2 = "hello world"; 67 String str3("test"); 68 69 cout << __LINE__ << ": " ; str2.print(); 70 cout << __LINE__ << ": " ; str3.print(); 71 72 String str4 = str3; 73 cout << __LINE__ << ": " ; str4.print(); 74 75 str4 = str2; 76 cout << __LINE__ << ": " ; str4.print(); 77 78 return 0; 79 }
5.单例模式复习
题解:感觉 singleton 还是写的有点点问题。先放着吧。这个务必学会手写。orz。
1 /* 2 * File: singleton.cc 3 * Time: 2018-12-19 10:22:18 4 * Author: wyzhang 5 */ 6 7 //要求: 在内存中只能创建一个对象 8 //1. 该对象不能是栈(全局)对象 9 //2. 该对象只能放在堆中 10 11 //应用场景: 12 //1. 直接替换任意的全局对象(变量)//因为全局对象越少越好 13 //2. 配置文件 14 //3. 词典类 15 16 //实现步骤: 17 //1. 将构造函数私有化 18 //2. 在类中定一个静态的指针对象(一般设置为私有),并且在类外初始化为空 19 //3. 定义一个返回值为类指针的静态成员函数, 20 // 如果2中的指针对象为空,则初始化对象,以后在有对象调用该静态成员函数的时候,不再初始化对象, 21 // 而是直接返回对象,保证类在内存中只有一个对象 22 23 24 #include <cstdio> 25 #include <iostream> 26 27 using std::cout; 28 using std::endl; 29 30 class Singleton { 31 public: 32 static Singleton * getInstance() { //不是静态的成员函数就没法调用,因为没有对象,需要类名调用 33 if (!_pInstance) { 34 _pInstance = new Singleton(); 35 } 36 return _pInstance; 37 } 38 static void destory() { 39 if (_pInstance) { 40 delete _pInstance; 41 } 42 } 43 void print() { 44 cout << "Singleton::print() " ; 45 if (_pInstance) { 46 printf("_pInstance = %p ", _pInstance); 47 } 48 } 49 private: 50 Singleton() { 51 cout << "Singleton()" << endl; 52 } 53 ~Singleton() { 54 cout << "~Singleton(): " ; 55 printf("_pInstance = %p ", _pInstance); 56 } 57 static Singleton * _pInstance; 58 }; 59 60 Singleton * Singleton::_pInstance = 0; 61 62 /* 63 wyzhang@IdeaPad:~/code/c++/20181214/homework$ ./singleton 64 Singleton() 65 p1 = 0x555eef444e70 66 p2 = 0x555eef444e70 67 Singleton::print() _pInstance = 0x555eef444e70 68 ~Singleton(): _pInstance = 0x555eef444e70 69 Singleton::print() _pInstance = 0x555eef444e70 70 ~Singleton(): _pInstance = 0x555eef444e70 71 */ 72 73 int main () { 74 Singleton * p1 = Singleton::getInstance(); 75 Singleton * p2 = Singleton::getInstance(); 76 printf("p1 = %p ", p1); 77 printf("p2 = %p ", p2); 78 79 p1->print(); 80 p1->destory(); 81 82 //可能写的有bug?能调用析构函数两次? 83 p2->print(); 84 p2->destory(); 85 86 return 0; 87 }
6.编写一个类,实现栈操作。
编写一个类,实现简单的栈。栈中有以下操作:
> 元素入栈 void push(int);
> 元素出栈 void pop();
> 读出栈顶元素 int top();
> 判断栈空 bool emty();
> 判断栈满 bool full();
如果栈溢出,程序终止。栈的数据成员由存放10个整型数据的数组构成。先后做如下操作:
> 创建栈
> 将10入栈
> 将12入栈
> 将14入栈
> 读出并输出栈顶元素
> 出栈
> 读出并输出栈顶元素
1 /* 2 * File: stack.cc 3 * Time: 2018-12-19 11:14:15 4 * Author: wyzhang 5 */ 6 7 #include <cstdio> 8 #include <iostream> 9 #include <vector> 10 11 using std::cout; 12 using std::endl; 13 using std::vector; 14 15 class Stack { 16 public: 17 Stack() { 18 19 } 20 Stack(int n) { 21 nums.reserve(n); //capacity 22 } 23 ~Stack() { 24 nums.clear(); 25 } 26 void push(int num) { 27 if (full()) { 28 cout << "can not push, stk is full. input is " << num << endl; 29 return; 30 } 31 nums.push_back(num); 32 } 33 void pop() { 34 if (empty()) { 35 cout << "can not pop, stk is empty." << endl; 36 return; 37 } 38 nums.pop_back(); 39 } 40 int top() { 41 if (empty()) { 42 cout << "can not get top, stk is empty." << endl; 43 return -1; 44 } 45 return nums.back(); 46 } 47 bool empty() { 48 return nums.size() == 0; 49 } 50 bool full() { 51 return nums.size() == nums.capacity(); 52 } 53 private: 54 vector<int> nums; 55 }; 56 57 int main () { 58 Stack stk = Stack(2); 59 stk.push(10); 60 stk.push(12); 61 stk.push(14); 62 63 cout << "top of stk is " << stk.top() << endl; 64 stk.pop(); 65 cout << "top of stk is " << stk.top() << endl; 66 67 cout << "stk is emtpy : " << stk.empty() << endl; 68 cout << "stk is full : " << stk.full() << endl; 69 return 0; 70 }
7.编写一个类,实现简单队列操作
编写一个类,实现简单的队列。队列中有以下操作:
> 元素入队 void push(int);
> 元素出队 void pop();
> 读取队头元素 int front();
> 读取队尾元素 int back();
> 判断队列是否为空 bool emty();
> 判断队列是否已满 bool full();
1 /* 2 * File: queue.cc 3 * Time: 2018-12-19 11:45:10 4 * Author: wyzhang 5 */ 6 7 #include <cstdio> 8 #include <iostream> 9 #include <vector> 10 11 using std::cout; 12 using std::endl; 13 using std::vector; 14 15 class Queue { 16 public: 17 Queue() 18 : first(0) 19 , rear(0) 20 , size(0) 21 , capacity(0) { 22 } 23 Queue(int num) 24 : first(0) 25 , rear(0) 26 , size(0) 27 , capacity(num) { 28 nums.reserve(num); 29 } 30 ~Queue() { 31 nums.clear(); 32 first = rear = -1; 33 size = capacity = 0; 34 } 35 void push(int num) { 36 if (full()) { 37 cout << "can not push element, queue is full. input is " << num << endl; 38 return; 39 } 40 ++size; 41 nums[rear] = num; 42 rear = (rear + 1) % capacity; 43 } 44 void pop() { 45 if (empty()) { 46 cout << "can not pop, queue is empty." << endl; 47 return; 48 } 49 --size; 50 first = (first + 1) % capacity; 51 } 52 int front() { 53 if (empty()) { 54 cout << "can not get front, queue is empty." << endl; 55 return -1; 56 } 57 return nums[first]; 58 } 59 int back() { 60 if (empty()) { 61 cout << "can not get back, queue is empty." << endl; 62 return -1; 63 } 64 int tmp_rear = ((rear + capacity) - 1) % capacity; 65 return nums[tmp_rear]; 66 } 67 bool empty() { 68 return size == 0; 69 } 70 bool full() { 71 return size == capacity; 72 } 73 74 private: 75 vector<int> nums; 76 int first, rear, size, capacity; 77 78 void print() { 79 printf("capacity = %d, size = %d, first = %d, rear = %d, [", capacity, size, first, rear); 80 for (int i = 0; i < capacity; ++i) { 81 printf(" %d", nums[i]); 82 } 83 printf("] "); 84 } 85 }; 86 87 int main () { 88 Queue que = Queue(3); 89 que.push(1); 90 printf("%d: que.front = %d, que.back = %d ", __LINE__, que.front(), que.back()); 91 que.push(2); 92 que.push(3); 93 que.push(4); 94 printf("%d: que.front = %d, que.back = %d ", __LINE__, que.front(), que.back()); 95 que.pop(); 96 printf("%d: que.front = %d, que.back = %d ", __LINE__, que.front(), que.back()); 97 98 printf("begin while loop "); 99 while (!que.empty()) { 100 printf("%d: que.front = %d, que.back = %d ", __LINE__, que.front(), que.back()); 101 que.pop(); 102 } 103 return 0; 104 }
8.封装Linux下互斥锁和条件变量
9. 实现只能生成栈对象的代码
10. 实现只能生成堆对象的代码
11. 统计一篇英文(The_Holy_Bible.txt)文章中出现的单词和词频
输入:某篇文章的绝对路径
输出:词典(词典中的内容为每一行都是一个“单词 词频”)
词典的存储格式如下
-----------------
| a 66 |
| abandon 77 |
| public 88 |
| ...... |
|_________________|
class Dictionary { public: //...... void read(const std::string & filename); void store(const std::string & filename); private: //...... };