【C++难点收录】“为什么C++难,你真的理解了这些吗?”《常见面试题》
🐧主頁詳情:個人主頁
📢作者簡介:🏅物聯(lián)網(wǎng)領(lǐng)域創(chuàng)作者🏅 and 🏅阿里專家博主🏅 and 🏅華為云享專家🏅
??人生格言:最慢的步伐不是跬步,而是徘徊;最快的腳步不是沖刺,而是堅持。
🧑?💻人生目標(biāo):成為一名合格的程序員,做未完成的夢:實現(xiàn)財富自由。
🚩技術(shù)方向:NULL
🀄如果覺得博主的文章還不錯的話,請三連支持一下博主哦
🏫系列專欄(免費):
1?? C語言進(jìn)階
2?? 數(shù)據(jù)結(jié)構(gòu)與算法(C語言版)
3?? Linux寶典
4?? C++從入門到精通
5?? C++從入門到實戰(zhàn)
6?? JavaScript從入門到精通
7??101算法JavaScript描述
8??微信小程序零基礎(chǔ)開發(fā)
9??牛客網(wǎng)刷題筆記
🔟計算機行業(yè)知識(補充)
文章目錄
- 前言
- 類與對象
- 隱含this指針
- 構(gòu)造函數(shù)和析構(gòu)函數(shù)
- 拷貝構(gòu)造
- 內(nèi)存管理
- operator new和operator delete函數(shù)
- 模板
- 引用的奇淫飛技
- string
- 模仿實現(xiàn)string
- 面試題
前言
眾所周知,C++為什么會難,到底難在哪里?小編通過本文告訴你;坑已填好,歡迎路過。
類與對象
臨時變量具有常性(函數(shù),引用)
內(nèi)聯(lián)函數(shù)沒有地址
隱含this指針
class Data { void Init(int year=0,int month=3,int day=4){_year=year;_month=month;_day=day;//類似于this->_year=year;this->_month=month;this->_day=day;}};this指針存在棧上,因為它是一個形參
#include<iostream> using namespace std;class A { public:void print(){cout << _a2 << endl;//this->_a2}void show(){cout << "show()"<<endl;} private:int _a2; };int main() {A *p = NULL;//p->print(p);p->print();//這一行會引發(fā)什么?編譯不通過?程序崩潰?正常運行?//崩潰p->show();//這一行會引發(fā)什么?編譯不通過?程序崩潰?正常運行?//正常運行//成員函數(shù)存在公共的代碼段,所以p->show()這里不會取p指向的對象上找//訪問成員函數(shù),才會去找A a;a.print();//p->print(&a);return 0; }構(gòu)造函數(shù)和析構(gòu)函數(shù)
Date d1;//我們不寫編譯器生成 2,全缺省的 3.無參數(shù)的 -》 默認(rèn)構(gòu)造函數(shù) -》不傳參數(shù)可以調(diào)用的我們不寫,編譯器生成構(gòu)造函數(shù)和析構(gòu)函數(shù)
內(nèi)置類型/基本類型 (int/char)不會處理
自定義類型 調(diào)用它的構(gòu)造函數(shù)初始化/析構(gòu)函數(shù)(new /delete)
class Time {~Time(){} }; class Date { private:int _size;int _capaciti;Time _t;//自定義類型};拷貝構(gòu)造
#include<iostream> using namespace std;class Date { public:Date(int year = 0, int month = 3, int day = 4){_year = year;_month = month;_day = day;}//err/*Date(Date d2){_year = d2._year;_month = d2._month;_day = d2._day;}*/ private:int _year;int _month;int _day; }; int main() {Date d1(2022,10,10);//這兩個寫法都調(diào)用拷貝構(gòu)造Date d2(d1);//調(diào)用拷貝構(gòu)造Date d3 = d2;return 0; }有三種傳參方式:
建議:傳入引用
Date(Date& d2)//類似于 Date &d2=d1;{_year = d2._year;_month = d2._month;_day = d2._day;}然后我們把賦值反過來:d2._day=__day;編譯運行通過了
這是因為我們把未初始的_day賦值給了d2,未初始化都是隨機值,這里也是引用,改變了原有的值,所以需要加上const防止改變
Date(const Date& d2)內(nèi)存管理
int globalVar = 1; static int staticGlobalVar = 1; void Test() { static int staticVar = 1; int localVar = 1; int num1[10] = {1, 2, 3, 4}; char char2[] = "abcd"; char* pChar3 = "abcd"; int* ptr1 = (int*)malloc(sizeof (int)*4); int* ptr2 = (int*)calloc(4, sizeof(int)); int* ptr3 = (int*)realloc(ptr2, sizeof(int)*4); free (ptr1); free (ptr3); } 1. 選擇題: 選項: A.棧 B.堆 C.數(shù)據(jù)段 D.代碼段 globalVar在哪里?____ staticGlobalVar在哪里?____ staticVar在哪里?____ localVar在哪里?____ num1 在哪里?____ char2在哪里?____ *char2在哪里?___ pChar3在哪里?____ *pChar3在哪里?____ ptr1在哪里?____ *ptr1在哪里?____答案:CCCAAAAADAB2. 填空題: sizeof(num1) = ____; sizeof(char2) = ____; strlen(char2) = ____; sizeof(pChar3) = ____; strlen(pChar3) = ____; sizeof(ptr1) = ____;答案:40,5,4,4/8,4,4/8【說明】
棧又叫堆棧,非靜態(tài)局部變量/函數(shù)參數(shù)/返回值等等,棧是向下增長的。
內(nèi)存映射段是高效的I/O映射方式,用于裝載一個共享的動態(tài)內(nèi)存庫。用戶可使用系統(tǒng)接口創(chuàng)建共享共
享內(nèi)存,做進(jìn)程間通信。(Linux課程如果沒學(xué)到這塊,現(xiàn)在只需要了解一下)
堆用于程序運行時動態(tài)內(nèi)存分配,堆是可以上增長的。
數(shù)據(jù)段–存儲全局?jǐn)?shù)據(jù)和靜態(tài)數(shù)據(jù)。
代碼段–可執(zhí)行的代碼/只讀常量。
操作系統(tǒng)內(nèi)存管理:
分段:不同用途數(shù)據(jù)放到不同的區(qū)域,就像我們現(xiàn)實中也會對省市鄉(xiāng)級劃分區(qū)域為一樣
分頁:
填空題
operator new和operator delete函數(shù)
我們相同的方法不同的操作來申請空間
A* p1=(A*)malloc(sizeof(A)); A* p2=new A;//會調(diào)用構(gòu)造函數(shù) A* p3 = (A*)operator new(sizeof(A));operator new和malloc的區(qū)別?我們先來看看申請空間錯誤的情況是什么樣,首先是malloc:
代碼:
size_t size = 2;//無符號類型 void *p4 = malloc(1024 * 1024 * 1024 * size);這里我們申請失敗返回0,我們再看operator new:
代碼:
void *p5 = operator new(1024 * 1024 * 1024 * size); cout << p5 << endl;這里失敗拋出異常(面對對象處理錯誤的方式)
結(jié)論:
使用方式都一樣,處理錯誤的方式不太一樣
operator new銷毀方式:operator delete(p5);
最后:這里我們要注意,為什么在.h文件不用包含任何頭文件庫和命名空間,是因為我在.cpp中包含頭文件的前面先加入了需要編譯后展開的using 和頭文件,如果命名空間放到文件后,string.h頭文件就會找不到cout和cin,因為需要展開才能使用到(先后問題)
模板
T ADD(T&a,T&b)ADD(a1,a2)T的類型是編譯器自己推導(dǎo)的(隱式),
ADD(a1,a2)(顯示)
我們用模板實現(xiàn)棧:
#include<iostream> #include<assert.h> using namespace std; template<class T> class stack { public:stack():_a(nullptr), _size(0), _capcity(0){}~stack(){delete[]_a;_a = nullptr;_size = 0;_capcity = 0;}//這里為了訪問私有通過公共接口返回_sizesize_t size(){return _size;}//類里面聲明,類外面定義stack<T>是一個類型T& operator[](size_t i);void push_back(const T&x);void pop_back();private:T*_a;size_t _size;size_t _capcity;};template<class T> T& stack<T>::operator[](size_t i) {assert(i <_size);return _a[i]; }template<class T>//聲明模板類型T,因為已經(jīng)與類分離了,函數(shù)類型是stack<T> void stack<T>::push_back(const T&x) {//空間不夠,需要增容if (_size == _capcity){int newcapticy = _capcity == 0 ? 2 : _capcity * 2;T*tmp = new T[newcapticy];//if (_a){//把舊的數(shù)據(jù)拷貝到新的空間memcpy(tmp, _a, sizeof(T)*_size);delete[]_a;}_a = tmp;_capcity = newcapticy;}_a[_size] = x;_size++; }template<class T> void stack<T>::pop_back() {assert(_size > 0);_size--; }int main() {stack<int>v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(6);for (size_t i = 0; i < v1.size(); i++){v1[i] *= 2;}for (size_t i = 0; i < v1.size(); i++){//這里的v1[i]會調(diào)用opertor[]第i個數(shù)據(jù)cout << v1[i]<<" ";}cout << endl;return 0; }引用的奇淫飛技
當(dāng)我們要修改函數(shù)返回的值,只能加上引用,否則會報錯“不可修改的左值”,還可以減少拷貝
string
模仿實現(xiàn)string
我們創(chuàng)建一個string.h頭文件
class string{public:string():_str(nullptr){}string(char*str) :_str(str){}size_t size(){return strlen(_str);}char& operator[](size_t i){return _str[i];}private:char* _str; };既然模仿,那肯定得有自己的sting,我們namespace一個名字,不然庫的string會搞混:
namespace copy {class string{.....}; }我們再寫一個測試函數(shù)變量字符串:
void test_string1(){string s2();string s1("helllo");for (size_t i = 0; i < s1.size(); i++){std::cout << s1[i] << " ";}std::cout << std::endl;}然后在.cpp源文件中這樣寫:
#include<iostream> #include"string.h" using namespace std;int main() {copy::test_string1(); }遍歷沒有問題,那我們修改呢?崩潰了,原因是函數(shù)都存在棧中,字符串在代碼段中,不能修改。
我們知道一個string的庫函數(shù)接口不止有修改還有插入還有空間不夠怎么辦?我們嘗試在堆上申請空間,讓指針指向這個堆,并且把字符串拷貝到堆里。
那應(yīng)該在哪里開辟一段空間呢?在構(gòu)造函數(shù)中,malloc和new可以完成,但這里是自定義類型,內(nèi)置類型它們效果一樣,所有我們用new,那申請多少個空間好呢?
string(char*str):_str(new char[strlen(str)+1]){}計算字符串大小,并+1,因為在我們使用字符串的時候,后面都帶有\(zhòng)0,而在在c_str接口的時候也是需要一個默認(rèn)\0;
最后拷貝:strcpy(_str, str)
然后,再實現(xiàn)一個空字符串遍歷,默認(rèn)的std::string s2;遍歷不會報錯,而我們的現(xiàn)在會崩潰,原因是,我們用this指針指向str這個空字符默認(rèn)只有\(zhòng)0我們對空解引用就出錯了;這里不能直接給他nullptr,new一個給他;
string():_str(new char[1]){_str[0] = '\0';}析構(gòu)函數(shù):
~string(){delete[]_str;_str = nullptr;} string s1("helllo"); string s2(s1);上面代碼會崩潰,原因是s2拷貝了s1的字符串和空間,因為我們沒有寫拷貝構(gòu)造函數(shù),編譯器默認(rèn)生成,這會造成同一個空間被析構(gòu)兩次【淺拷貝】
深拷貝:(把~string(){}注釋掉,不然一樣報錯)
//s2=s1;string& operator=(const string& s){if (this != &s){char*tmp = new char[strlen(s._str) + 1];strcpy( tmp,s._str);delete[]_str;_str = tmp;}return *this;}底層原理:
using namespace std;namespace test{class string{public:typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//這里需要對它解引用,不能直接nullptr(默認(rèn)為空)string(char* str="") :_str(new char[strlen(str)+1]){strcpy( _str,str);}//傳統(tǒng)手法//s2(s1)/*string(const string &s):_str(new char [strlen(_str)+1]){strcpy(_str, s._str);}//現(xiàn)代手法//s2(s1)string(string & s) :_str(nullptr){string tmp(s._str);swap(_str, s._str);}//s2=s3;string&operator=(string s){swap(_str, s._str);return *this;}//const s2=s3;string&operator=(const string& s){if (this != &s){string tmp(s._str);swap(_str,tmp._str);//等同swap(_str, s._str);return *this;}}string(){}size_t size(){return strlen(_str);}const char* c_str(){return _str;}char& operator[](const size_t& i){return _str[i];}~string(){delete[]_str;_str = nullptr;}*/private:char* _str;size_t _size;size_t _capacity;};void test_string1(){string s1("hello world");string s3="world";for (size_t i = 0; i < s1.size(); i++){s1[i] += 1;cout << s1[i]<< " ";}cout << endl;string s2 = s3;cout << s2.c_str() << endl;}void test_string2(){string s1("hello world");string s2(s1);cout << s2.c_str() << endl;}void test_string3(){string s1("hello world");string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : s1){cout << e << " ";}cout << endl;}}面試題
一個字節(jié)不是為了存取數(shù)據(jù),而是占位
當(dāng)我們實例化一個對象,A1 a;我們需要用它,如果字節(jié)0,那我們怎么找它調(diào)用呢?
class A1 { void printf(); }; int main() {A1 a;return 0; }全局變量和static變量有什么區(qū)別?
malloc/calloc/realloc有什么取別
? malloc:申請空間
? calloc:申請空間加初始化為0;
? realloc:對已有的空間進(jìn)行擴容
malloc/free和new/delete的區(qū)別
malloc/free和new/delete的共同點是:都是從堆上申請空間,并且需要用戶手動釋放。不同的地方是:
malloc和free是函數(shù),new和delete是操作符
malloc申請的空間不會初始化,new可以初始化
malloc申請空間時,需要手動計算空間大小并傳遞,new只需在其后跟上空間的類型即可
malloc的返回值為void*, 在使用時必須強轉(zhuǎn),new不需要,因為new后跟的是空間的類型
malloc申請空間失敗時,返回的是NULL,因此使用時必須判空,new不需要,但是new需要捕獲異常
申請自定義類型對象時,malloc/free只會開辟空間,不會調(diào)用構(gòu)造函數(shù)與析構(gòu)函數(shù),而new在申請空間
后會調(diào)用構(gòu)造函數(shù)完成對象的初始化,delete在釋放空間前會調(diào)用析構(gòu)函數(shù)完成空間中資源的清理
出現(xiàn)內(nèi)存泄漏,影響很大,如操作系統(tǒng)、后臺服務(wù)等等,出現(xiàn)內(nèi)存泄漏會
導(dǎo)致響應(yīng)越來越慢,最終卡死
內(nèi)存泄漏
什么是內(nèi)存泄漏:內(nèi)存泄漏指因為疏忽或錯誤造成程序未能釋放已經(jīng)不再使用的內(nèi)存的情況。內(nèi)存泄漏并不
是指內(nèi)存在物理上的消失,而是應(yīng)用程序分配某段內(nèi)存后,因為設(shè)計錯誤,失去了對該段內(nèi)存的控制,因而
造成了內(nèi)存的浪費。
內(nèi)存泄漏的危害:長期運行的程序
如何避免內(nèi)存泄漏
1工程前期良好的設(shè)計規(guī)范,養(yǎng)成良好的編碼規(guī)范,申請的內(nèi)存空間記著匹配的去釋放。ps:這個理想狀
態(tài)。但是如果碰上異常時,就算注意釋放了,還是可能會出問題。需要下一條智能指針來管理才有保
證.
7.2 采用RAII思想或者智能指針來管理資源。
7.3 有些公司內(nèi)部規(guī)范使用內(nèi)部實現(xiàn)的私有內(nèi)存管理庫。這套庫自帶內(nèi)存泄漏檢測的功能選項。
7.4 出問題了使用內(nèi)存泄漏工具檢測。ps:不過很多工具都不夠靠譜,或者收費昂貴。
總結(jié)一下:
內(nèi)存泄漏非常常見,解決方案分為兩種:1、事前預(yù)防型。如智能指針等。2、事后查錯型。如泄漏檢測工
具。
如何一次在堆上申請4G的內(nèi)存?
(增容次數(shù)更多,效率更低,因為每次增容都要付出代價)
2倍:相對而言效率更好,但是浪費的空間更多。插入1067個數(shù)據(jù),(倒數(shù)最后一個1024*2)最終到2048,浪費970多個
1.5倍:1067個數(shù)據(jù),總?cè)萘?599(倒數(shù)一個1066*1.5)
答:增容多少是一種旋轉(zhuǎn),各有利弊,均衡一點,可以使用reserve(1067);
vector的缺點是什么?
答:1.頭部和中部的插入刪除效率低,O(N),因為需要挪動數(shù)據(jù);
? 2.插入數(shù)據(jù)空間不勾 需要增容,增容需要開新空間、拷貝數(shù)據(jù)、釋放舊空間,會付出很大的代價
優(yōu)點:
1.支持下標(biāo)的隨機訪問、間接的就很好的支持排二分查找、堆算法等等
list出現(xiàn)就是為了解決vector的缺陷
優(yōu)點:
1.list頭部、中間插入不再需要挪動數(shù)據(jù),效率高O(1)
2.list插入數(shù)據(jù)是新增節(jié)點,不再需要增容
缺點:
1.不支持隨機訪問。
所有實際使用中vector和list是相互相成的兩個容器
- 如果對大家有幫助,請三連支持一下!
- 有問題歡迎評論區(qū)留言,及時幫大家解決!
總結(jié)
以上是生活随笔為你收集整理的【C++难点收录】“为什么C++难,你真的理解了这些吗?”《常见面试题》的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 框架运行错误:Ljava/lang/St
- 下一篇: C、C++、Java回顾