一些知識點

 

1、  簡述你對模板方法的設計模式的瞭解

 

模式定義:

 

        模板方法模式在一個方法中定義了一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠在不改變算法結構的情況下,又一次定義算法中的某些步驟。

        模板就是一個方法。更詳細的說。這種方法將算法定義成一組步驟。當中的不論什麼步驟都能夠是抽象的,由子類實現。

這能夠確保算法的結果保持不變,同一時候由子類提供部分實現。

 

 

 

 

 

 

 

2、  結構體字節對齊的問題

 

結構體字節對齊的細節和具體編譯器實現相關,但一般而言滿足三個準則:

     1) 結構體變量的首地址能夠被其最寬基本類型成員的大小所整除;

     2) 結構體每個成員相對結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要編譯器會在成員之間加上填充字節(internal adding);

     3) 結構體的總大小爲結構體最寬基本類型成員大小的整數倍,如有需要編譯器會在最末一個成員之後加上填充字節{trailing padding}。

     對於以上規則的說明如下:

     第一條:編譯器在給結構體開闢空間時,首先找到結構體中最寬的基本數據類型,然後尋找內存地址能被該基本數據類型所整除的位置,作爲結構體的首地址。將這個最寬的基本數據類型的大小作爲上面介紹的對齊模數。

     第二條:爲結構體的一個成員開闢空間之前,編譯器首先檢查預開闢空間的首地址相對於結構體首地址的偏移是否是本成員大小的整數倍,若是,則存放本成員,反之,則在本成員和上一個成員之間填充一定的字節,以達到整數倍的要求,也就是將預開闢空間的首地址後移幾個字節。

     第三條:結構體總大小是包括填充字節,最後一個成員滿足上面兩條以外,還必須滿足第三條,否則就必須在最後填充幾個字節以達到本條要求。

      數據結構的成員位置要兼顧成員之間的關係、數據訪問效率和空間利用率。順序安排原則是:四字節的放在最前面,兩字節的緊接最後一個四字節成員,一字節緊接最後一個兩字節成員,填充字節放在最後。

 

 

 

typedef struct{
    char  a;
    short b;
    char  c;
    int   d;
    char  e[3];
}T_Test;

  首先char a佔用1個字節,沒問題。

     short b本身佔用2個字節,根據上面準則2,需要在b和a之間填充1個字節。

     char c佔用1個字節,沒問題。

     int d本身佔用4個字節,根據準則2,需要在d和c之間填充3個字節。

     char e[3];本身佔用3個字節,根據原則3,需要在其後補充1個字節。

     因此,sizeof(T_Test) = 1 + 1 + 2 + 1 + 3 + 4 + 3 + 1 = 16字節。

 

 

 

 

 

 

3、 寫代碼,用最簡單的方法實現複製數組a[100]到b[100];(memcpy)

 

 

#include<iostream>
#include<string.h>
using namespace std;
int main()
{
	char a[100]={'a','b','c','d','e'};
	int b[100];
	cout<<"sizeof is : "<<sizeof(a)<<endl;//計算'\0'
	cout<<"strlen is : "<<strlen(a)<<endl;//不計算'\0'
	//1、如果複製的字節數n超出了dest的空間容量,或者n超出src的容量,這個函數是不會進行判斷的,這樣就會很危險。需要程序員自己檢查是否有溢出的情況出現。
	//2、這個函數不會檢查參數dest與參數src所指向的數組(或其他類型)是否具有同樣的空間。
	//3、memcpy, memset等函數都是對內存操作的函數,效率很高,當然也很容易出現問題;如果出現src -> dest的大小出現問題,src地址大於dest地址,就會存在dest無法存取完整數據,造成src數據丟下。memcpy本身是有bug的,並沒有解決覆蓋問題,可以選用memmove代替。
	memcpy(b,a,sizeof(a));
	//不能拷貝string類型,sizeof(string)只是求了固定大小的成員的內存和,而沒有求string內部指針指向的存字符的那一段內存
	//如果結構體含有指針,指向某段內存,memcpy的拷貝也會失敗
	//對象不能是memcpy簡單拷貝,t2 拷貝後,其指針指向所存放的字符串,該字符串所存放地址與t1中是相同的。程序結束的時候,t1,t2分別析構,由於t1和t2指向同一字符串內存,於是該內存釋放兩次,於是dump。所以,對象的賦值要經過拷貝構造函數定義。
	return 0;
}

4、  代碼實現對a[100]的插入排序;

算法實現

#include "stdafx.h"
#include<iostream>
using namespace std;
void InsertSort(int a[], int n)
{
	for (int j = 1; j < n; j++)
	{
		int key = a[j]; //待排序第一個元素
		int i = j - 1;  //代表已經排過序的元素最後一個索引數
		while (i >= 0 && key < a[i])
		{
			//從後向前逐個比較已經排序過數組,如果比它小,則把後者用前者代替,
			//其實說白了就是數組逐個後移動一位,爲找到合適的位置時候便於Key的插入
			a[i + 1] = a[i];
			i--;
		}
		a[i + 1] = key;//找到合適的位置了,賦值,在i索引的後面設置key值。
	}
}

 

 

 


5、 代碼實現在a[100]裏查找的某個值的位置(二分查找法)。必須爲有序

 

 

 

 

int binary_research(int arr[],int left,int right,int element)  
{  
    while(left<=right)  
    {     
        int mid = (left+right)/2;  
        if(arr[mid]>element)  
        {  
            right = mid - 1;  
        }  
        else if(arr[mid]<element)  
        {  
            left = mid + 1;  
        }  
        else   
        {  
            return mid;  
        }  
    }  
    return -1;  
}  

 

 

 

 

 

 



6、  定義一天有多少秒(定義long)#define A ***L或const

#define  SEC_PER_YEAR  (111255555555555560UL)
#define  SEC_PER_YEAR  (1UL*356*24*60*60)
#define  SEC_PER_YEAR  (356*24*60*60UL)會出錯,因爲356*24*60會溢出int類型的長度






5、  完善一個Person類,實現堆棧常用功能,例如出入棧、獲得棧頂等,還有實現SetName或GetName等功能。(注意string的使用,構造函數和析構函數,拷貝函數等,更多的還可以重載=運算符)。

 

拷貝構造函數:

classname(constclassname &obj){// 構造函數的主體}

 

  • 通過使用另一個同類型的對象來初始化新創建的對象。

  • 複製對象把它作爲參數傳遞給函數。

  • 複製對象,並從函數返回這個對象。

  • "="賦值符號左邊的變量如果是已經聲明的,調用了構造函數,便不會調用拷貝構造函數,而是調用重載的"="賦值符號。

  • 調用拷貝構造函數進行初始化的時候,並不會調用“="重載。

 把參數傳遞給函數有三種方法,一種是值傳遞,一種是傳地址,還有一種是傳引用。前者與後兩者不同的地方在於:當使用值傳遞的時候,會在函數裏面生成傳遞參數的一個副本,這個副本的內容是按位從原始參數那裏複製過來的,兩者的內容是相同的。當原始參數是一個類的對象時,它也會產生一個對象的副本,不過在這裏要注意。一般對象產生時都會觸發構造函數的執行,但是在產生對象的副本時卻不會這樣,這時執行的是對象的複製構造函數。爲什麼會這樣?嗯,一般的構造函數都是會完成一些成員屬性初始化的工作,在對象傳遞給某一函數之前,對象的一些屬性可能已經被改變了,如果在產生對象副本的時候再執行對象的構造函數,那麼這個對象的屬性又再恢復到原始狀態,這並不是我們想要的。所以在產生對象副本的時候,構造函數不會被執行,被執行的是一個默認的構造函數。當函數執行完畢要返回的時候,對象副本會執行析構函數,如果你的析構函數是空的話,就不會發生什麼問題,但一般的析構函數都是要完成一些清理工作,如釋放指針所指向的內存空間。這時候問題就可能要出現了。假如你在構造函數裏面爲一個指針變量分配了內存,在析構函數裏面釋放分配給這個指針所指向的內存空間,那麼在把對象傳遞給函數至函數結束返回這一過程會發生什麼事情呢?首先有一個對象的副本產生了,這個副本也有一個指針,它和原始對象的指針是指向同塊內存空間的。函數返回時,對象的析構函數被執行了,即釋放了對象副本里面指針所指向的內存空間,但是這個內存空間對原始對象還是有用的啊,就程序本身而言,這是一個嚴重的錯誤。然而錯誤還沒結束,當原始對象也被銷燬的時候,析構函數再次執行,對同一塊系統動態分配的內存空間釋放兩次是一個未知的操作,將會產生嚴重的錯誤。

       上面說的就是我們會遇到的問題。解決問題的方法是什麼呢?首先我們想到的是不要以傳值的方式來傳遞參數,我們可以用傳地址或傳引用。沒錯,這樣的確可以避免上面的情況,而且在允許的情況下,傳地址或傳引用是最好的方法,但這並不適合所有的情況,有時我們不希望在函數裏面的一些操作會影響到函數外部的變量。那要怎麼辦呢?可以利用複製構造函數來解決這一問題。複製構造函數就是在產生對象副本的時候執行的,我們可以定義自己的複製構造函數。在複製構造函數裏面我們申請一個新的內存空間來保存構造函數裏面的那個指針所指向的內容。這樣在執行對象副本的析構函數時,釋放的就是複製構造函數裏面所申請的那個內存空間。

       除了將對象傳遞給函數時會存在以上問題,還有一種情況也會存在以上問題,就是當函數返回對象時,會產生一個臨時對象,這個臨時對象和對象的副本性質差不多。

拷貝構造函數,經常被稱作X(X&),是一種特殊的構造函數,他由編譯器調用來完成一些基於同一類的其他對象的構件及初始化。它的唯一的一個參數(對象的引用)是不可變的(因爲是const型的)。這個函數經常用在函數調用期間於用戶定義類型的值傳遞及返回。拷貝構造函數要調用基類的拷貝構造函數和成員函數。如果可以的話,它將用常量方式調用,另外,也可以用非常量方式調用。 

在C++中,下面三種對象需要拷貝的情況。因此,拷貝構造函數將會被調用。 

1). 一個對象以值傳遞的方式傳入函數體 

2). 一個對象以值傳遞的方式從函數返回 

3). 一個對象需要通過另外一個對象進行初始化 

以上的情況需要拷貝構造函數的調用。如果在前兩種情況不使用拷貝構造函數的時候,就會導致一個指針指向已經被刪除的內存空間。對於第三種情況來說,初始化和賦值的不同含義是構造函數調用的原因。事實上,拷貝構造函數是由普通構造函數和賦值操作賦共同實現的。描述拷貝構造函數和賦值運算符的異同的參考資料有很多。 

拷貝構造函數不可以改變它所引用的對象,其原因如下:當一個對象以傳遞值的方式傳一個函數的時候,拷貝構造函數自動的被調用來生成函數中的對象。如果一個對象是被傳入自己的拷貝構造函數,它的拷貝構造函數將會被調用來拷貝這個對象這樣複製纔可以傳入它自己的拷貝構造函數,這會導致無限循環。 

除了當對象傳入函數的時候被隱式調用以外,拷貝構造函數在對象被函數返回的時候也同樣的被調用。換句話說,你從函數返回得到的只是對象的一份拷貝。但是同樣的,拷貝構造函數被正確的調用了,你不必擔心。 

如果在類中沒有顯式的聲明一個拷貝構造函數,那麼,編譯器會私下裏爲你制定一個函數來進行對象之間的位拷貝(bitwise copy)。這個隱含的拷貝構造函數簡單的關聯了所有的類成員。許多作者都會提及這個默認的拷貝構造函數。注意到這個隱式的拷貝構造函數和顯式聲明的拷貝構造函數的不同在於對於成員的關聯方式。顯式聲明的拷貝構造函數關聯的只是被實例化的類成員的缺省構造函數除非另外一個構造函數在類初始化或者在構造列表的時候被調用。 

拷貝構造函數是程序更加有效率,因爲它不用再構造一個對象的時候改變構造函數的參數列表。設計拷貝構造函數是一個良好的風格,即使是編譯系統提供的幫助你申請內存默認拷貝構造函數。

 

 

 

事實上,拷貝構造函數是由普通構造函數和賦值操作符共同實現的,

通常的原則是:①對於凡是包含動態分配成員或包含指針成員的類都應該提供拷貝構造函數;②在提供拷貝構造函數的同時,還應該考慮重載"="賦值操作符號。

執行的是淺拷貝,只是將成員的值進行賦值,也即這兩個指針指向了堆裏的同一個空間,在銷燬對象時,兩個對象的析構函數將對同一個內存空間釋放兩次,這就是錯誤出現的原因

在“深拷貝”的情況下,對於對象中動態成員,就不能僅僅簡單地賦值了,而應該重新動態分配空間

 

// testSingleMode.cpp : 定義控制檯應用程序的入口點。  
//  
#include "stdafx.h"  
#include <iostream>  
  
using namespace std;  
  
class Complex  
{  
private:  
    double m_real;  
    double m_imag;  
  
public:  
    Complex(void){  
        m_real = 0.0;  
        m_imag = 0.0;  
    }  
  
    Complex(double real, double imag){  
        m_real = real;  
        m_imag = imag;  
    }  
  
    Complex(const Complex & c){ //這裏就是最經典的拷貝構造函數了  
        m_real = c.m_real;  
        m_imag = c.m_imag;  
    }  
  
    Complex &operator = (const Complex &rhs){   //這裏就是最經典的operator=操作符重載了  
        if (this == &rhs){  
            return *this;  
        }  
  
        this->m_real = rhs.m_real;  
        this->m_imag = rhs.m_imag;  
  
        return *this;  
    }  
  
    explicit Complex::Complex(double r){    //explicit的用法,只適用於1個參數的情況  
        m_real = r;  
        m_imag = 0.0;  
    }  
  
};  
  
  
int main()  
{  
    Complex c1, c2; //調用 第15行 默認無參數的構造函數  
    Complex c3(1.0, 2.5);   //調用 第20行 具有2個形參的構造函數  
    //Complex c3 = Complex(1.0, 2.5);   //和上一行是一個意思,所以這個註釋了  
    c1 = c3;    //調用 第30行 重載operator=運算符  
    c2 = c3;    //調用 第30行 重載operator=運算符  
    //c2 = 5.2; //隱式轉換,需要去掉41行的explicit關鍵字,纔可編譯通過  
  
    Complex c5(c2);     //調用 第25行 拷貝構造函數  
    Complex c4 = c2;    //調用 第25行 拷貝構造函數  
  
    getchar();  
    return 0;  
}  


構造函數和析構函數:

 

構造函數不可以是虛函數的,這個很顯然,畢竟虛函數都對應一個虛函數表,虛函數表是存在對象內存空間的,如果構造函數是虛的,就需要一個虛函數表來調用,但是類還沒實例化沒有內存空間就沒有虛函數表,這根本就是個死循環。

 

 

派生類對象構造的時候先調用基類的構造函數再調用派生類的構造函數,析構的時候先調用派生類析構函數再調用基類析構函數。

其實這個很好理解,派生類的成員由兩部分組成,一部分是從基類那裏繼承而來,一部分是自己定義的。那麼在實例化對象的時候,首先利用基類構造函數去初始化從基類繼承而來的成員,再用派生類構造函數初始化自己定義的部分。

同時,不止構造函數派生類只負責自己的那部分,析構函數也是,所以派生類的析構函數會只析構自己的那部分,這時候如果基類的析構函數不是虛函數,則不能調用基類的析構函數析構從基類繼承來的那部分成員,所以就會出現只刪一半的現象,造成內存泄漏。

所以析構函數要定義成虛函數。

 

 

 

 

 

簡單的棧操作

#ifndef CSTOCK_H_  
#define CSTOCK_H_  
  
const int STOCK_SIZE = 100;//定義棧的大小  
typedef int elemType;//定義棧元素類型,目前僅用int來練手  
  
class CStock  
{  
public:  
    CStock(); //構造函數,構造空棧;  
  
    bool push(elemType x); //進棧操作;  
    bool pop(elemType &x); //出棧操作;  
    void clear(); //清空棧;  
    bool isEmpty(); //判斷是否棧空;  
    bool isFull(); //判斷是否棧滿;  
    void print(); //打印棧內元素;  
  
    ~CStock();  
  
private:  
   elemType elem[STOCK_SIZE];  
   int top;//指向棧頂;  
};  
  
#endif  
#include "Stock.h"  
#include<iostream>  
using std::cout;  
using std::endl;  
  
//構造函數  
CStock::CStock():top(-1)  
{  
  
}  
  
//進棧操作,進棧成功返回true,否則返回false;  
bool CStock::push(elemType x)  
{  
   if(top == STOCK_SIZE - 1)  
   {  
       return false;  
   }  
   elem[++top] = x;  
   return true;  
}  
  
//出棧操作,由形參x將元素帶出主調函數,出棧成功返回true,否則返回false;  
bool CStock::pop(elemType &x)  
{  
   if(top == -1)  
   {  
       return false;  
   }  
   x = elem[top--];  
   return true;  
}  
  
//清空棧,使棧爲空;  
void CStock::clear()  
{  
    top = -1;  
}  
  
//判斷棧是否爲空  
bool CStock::isEmpty()  
{  
    return top == -1;  
}  
  
//判斷棧是否棧滿  
bool CStock::isFull()  
{  
    return top == STOCK_SIZE - 1;  
}  
  
//打印棧  
void CStock::print()  
{  
   for(int i = 0; i <= top; i++)  
   {  
       cout << elem[i] << "\t";  
  
       if( (i+1) % 5 == 0)  
           cout << endl;  
   }  
}  
//析構函數  
CStock::~CStock(void)  
{  
  
}  

 單例模式加鎖例子

class singleton
{
protected:
    singleton()
    {
        pthread_mutex_init(&mutex);
    }
private:
    static singleton* p;
public:
    static pthread_mutex_t mutex;
    static singleton* initance();
};

pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;
singleton* singleton::initance()
{
    pthread_mutex_lock(&mutex);
    if (p == NULL)
        p = new singleton();
    pthread_mutex_unlock(&mutex);
    return p;
}

 

int a[2] = {1,2};  
int main(){  
        printf("a = %p\n", a); // I  
        printf("&a = %p\n", &a); // II  
        printf("a + 1 = %p\n", a + 1);// III  
        printf("&a + 1 = %p\n", &a + 1);// IV  
  
        return 0;  
}  
a = 0x804a014
&a = 0x804a014
a + 1 = 0x804a018
&a + 1 = 0x804a01c

 

a和&a有什麼區別

在C中, 在幾乎所有使用數組的表達式中,數組名的值是個指針常量,也就是數組第一個元素的地址。 它的類型取決於數組元素的類型: 如果它們是int類型,那麼數組名的類型就是“指向int的常量指針“。


對於II 和 IV 則是特殊情況,在《C和指針》p142中說到,在以下兩中場合下,數組名並不是用指針常量來表示,就是當數組名作爲sizeof操作符和單目操作符&的操作數時。 sizeof返回整個數組的長度,而不是指向數組的指針的長度。 取一個數組名的地址所產生的是一個指向數組的指針,而不是一個指向某個指針常量的指針。
所以&a後返回的指針便是指向數組的指針,跟a(一個指向a[0]的指針)在指針的類型上是有區別的。

 

以字符串形式出現的,編譯器都會爲該字符串自動添加一個0作爲結束符,如在代碼中寫
  "abc",那麼編譯器幫你存儲的是"abc\0"

 

char *str1 = "asdfgh";//雙引號做了1:申請了空間(常量區)存放字符串,2:在字符串後面加上'\0',返回地址,str1存放的地址是變量。
char str2[] = "asdfgh";//因爲定義的是一個字符數組,所以相當於定義了一些空間來存放"asdfgh",編譯器把這個語句解析爲char str2[] ={'a','s','d','f','g','h','\0'}。但char str2[7],str2="asdfgh"則錯誤,"asdfgh"返回地址,而str2存放的地址是常量(指向數組的首元素地址),不能給常量賦值,並且因爲定義的是一個普通字符指針,並沒有定義空間存放"asdfgh",所以編譯器會幫我們找地方存放字符串,會把"asdfgh"當成常量並放在程序的常量區
char str3[8] = {'a', 's', 'd'};
char str4[] = "as\0df";
執行結果是:
sizeof(str1) = 4;  strlen(str1) = 6;
sizeof(str2) = 7;  strlen(str2) = 6;
sizeof(str3) = 8;  strlen(str3) = 3;
sizeof(str4) = 6;  strlen(str4) = 2;

str1是字符指針變量,sizeof獲得的是該指針所佔的地址空間,32位操作系統對應4字節,所以結果是4;strlen返回的是該字符串的長度,遇到'\0'結束,'\0'本身不計算在內,故結果是6。

str2是字符數組,大小由字符串常量"asdfgh"確定,sizeof獲得該數組所佔內存空間大小,包括字符串結尾的'\0',所以結果爲7;strlen同理返回6。

str3也是字符數組,但大小確定爲8,故sizeof得到的結果是8;strlen統計'\0'之前所有字符的個數,即爲3;

str4是常量字符數組,sizeof得到字符總數即6;strlen計算至'\0'結束,因此返回2;

當數組名作爲sizeof操作符和單目操作符&的操作數時。 sizeof返回整個數組的長度。

總結一句就是:sizeof計算的是變量的大小,包括'\0'在內,而strlen計算的是字符串的長度,以'\0'作爲長度結束判斷,不計算'\0'。

 

數組可以用sizeof來得到它的大小,但是如果是指針,就不能使用sizeof來得到大小。因爲數組再定義時,已經指明瞭它的大小,所以編譯器可以知道它的實際大小,而對一個指針來說,由於是動態分配的內存,編譯器怎麼知道它的大小呢?所以不能對指針用sizeof來得到分配的內存大小,此時用sizeof得到的僅僅是指針佔用的內存字節數。對Win32程序來說,指針佔用4個字節。

 

 

Tcp通信方式:

爲什麼需要四次斷開:

TCP支持雙工通信,即收、發兩個方向各自獨立工作,一個FIN+ACK只能斷開一個方向的通信,當要斷開兩個方向的通信時就需要兩個FIN+ACK,即四次操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章