天天看點

【C++筆記】對C++的簡單總結(一)

1、 在 C++ ,不同類型的指針是不能直接指派的,必須強轉

void *p;
int *i = (int *)p;
           

2、class是C++的核心,也是面向對象的核心基礎

class Person
{
public:
     string name;
private:
     int age;
public:
     int sex;
};
           

3、引入了命名空間防止大型項目中代碼沖突 4、new關鍵字的使用

int *p = new int;
*p = 100;
//配置設定記憶體的時候可以同時指派,前兩句代碼相當于 int * p = new int (100);

cout << *p << endl;//輸出100
delete p;//用完記得删除, 用new配置設定的記憶體必須用delete釋放,不能用free

int *p = new int[10];//用new建立數組
               //删除的時候不能這樣寫 :delete p; 這樣寫是删除了數組中第一個位址,會造成記憶體洩漏;
delete []p;
           

5、inline 内聯關鍵字的作用:内聯函數不用為函數調用,而是直接把函數的代碼嵌入到調用的語句中。

int main(){
  for ( int i = 0 ; i < 100; i++)
  {
     int a = max (i , 50 );
  }
  return 0 ;
}

inline int max (int a, int b ){
     if ( a > b)
       return a ;
     else
       return b ;
}
//inline函數适合用在代碼少,且大量的調用的函數。
           

6、引用:一個變量的别名,不是一個變量的位址

int main(){
  int a = 1 ; int b = 2 ;
 
  int & c = a; //正确,c就是a的别名
 
  c = b; //錯誤,不能修改
 
  int & c;
  c = a; //錯誤,c不能單獨命名
 
  swap ( a , b );
  cout << "a = " << a << endl ; // a = 2;
  cout << "b = " << b << endl ; // b = 1;
  return 0 ;
}

void swap( int & a , int & b ){
  int temp = a ;
  a = b;
  b = temp;
}
           

7、函數的預設實參

void Func( int a = 100 ){
   cout << "a = " << a << endl ;
}
int main(){
     Func (); // 列印 a = 100;
     return 0 ;
}
           

8、函數重載:函數名字相同,隻是參數不同

void Func(){}
void Func( int a ){}
void Func( string str ){}
           

9、模闆函數

template <class T>
T Add( T a , T b)
{
     return a + b ;
}
int main()
{
     int a = 1;
     int b = 2;
     Add( a , b );
     return 0;
}
           

10、private,public,protected 在類裡面如果沒有權限限定符,預設是private 在結構體裡面沒有權限限定符,預設是public 11、一個unsigned int類型與int類型相加的結果是一個unsigned int類型。

int main()
{
     int unsigned a = 1 ;
     int b = - 2;
     std ::cout << a + b << std :: endl;//輸出的類型是一個unsigned int類型,輸出結果為:4294967295
}
           

12、兩個字元串位置相鄰,實際上是一個整體。

std :: cout << "asdfasdf"   "asdfasdf"     "ASdfasd" << std:: endl ;
//輸出的是一個整體
           

13、定義在函數體内部的 内置類型變量不會被初始化,定義在函數體外部的 内置類型變量會自動初始化。

int main()
{
     int a ;
     std ::cout << a << std:: endl ;
     return 0 ;
}
//程式會報錯,提示局部變量a未被初始化。
           

14、 extern關鍵字

extern int i;//聲明i
int j;//聲明并定義j
extern int k = 1;//定義
           

15、 引用就是别名,引用不是對象,它隻是一個已存在的對象的别名,一旦綁定一個對象之後就無法再綁定其他對象。

int main()
{
     int b = 0;
     int &a = b ;//a指向b
     int &c;//報錯,引用必須初始化
     int &d = 1;//報錯,引用隻能綁定到對象上。
     std ::cout << &b << std :: endl;
     std ::cout << &a << std :: endl;//a和b都指向同一個位址
      return 0 ;
}
           

16、void*是一種特殊的指針類型,可以存儲任意對象的位址。

int main()
{
     int val = 0 ;
     void *a = &val ;//合法
     long *b = &val;//非法
}
           

17、const對象在預設情況下隻在本檔案内有效。若要共享const對象,就必須在前面加上extern關鍵字

extern const int val = 0;//定義了并初始化了一個常量,這個常量可以在其他檔案中使用。

extern const int val;//聲明了一個val,說明val并不是本檔案獨有的,它的定義在别處。
           

18.const指針

int main()
{
     int val = 1 ;
     int num = 2 ;
     int * const p = & val; //p是一個const指針,p将一直指向val,可以修改所指對象的值(也就是val的值)
     p = &num ; //錯誤,p是一個const指針

     const int num1 = 1 ;
     const int *const p1 = & num1 ;//p1是一個指向常量對象的常量指針,既不能指向其他位址,也不能修改所指對象的值(也就是num1的值),
}
//如果表達式太複雜,可以通過從右往左閱讀來弄清楚。拿上面例子來講:p旁邊的符号是const,說明p是一個常量對象,再往左的符号是*,表示p是一個常量指針,再往左是int,則說明p是一個指向int類型對象的常量指針。
//通過上面方法可以得知p1是一個指向int類型常量的常量指針。
           

19、關于指向常量的指針和引用。

int main()
{
     int val = 1 ;
     const int *p1 = & val; //正确,可以指向一個非常量對象
     const int &a = val ; //正确,可以綁定一個非常量對象
}
//1、指向常量的指針可以指向非常量,但不可以通過該指針來修改這個非常量的值。
//2、常量引用也可以綁定非常量對象,但是不可以通過該引用來對綁定的這個對象作出修改。
           

20、字元串的幾種初始化方法,一般使用常用方法即可。

int main()
{
     string s = "aaaa" ; //常用
     string s1 ( 10, 'a') ;//輸出10個a
     string s2 = { "aaaa" };
     string s3 = ( "aaaa" );
     string s4 ( "aaaa" );
     string s5 = ( "aaaa" );
     string s6 = string( "aaaa" ) ;
}
//若使用=來初始化則表示<strong>拷貝初始化</strong>,不使用=初始化表示<strong>直接初始化</strong>
           

21、getline會讀取一整行,如果希望在得到的字元串中保留白格,就用getline替換>>運算符。

int main()
{
     string s ;
     while ( getline (cin , s))//讀入一整行直到遇到檔案末尾
     {
          cout << s << endl ;
     }
     return 0 ;
}
           

22、字面值和string對象相加時,必須確定加号兩邊至少有一個是string對象

int main()
{
     string s1 = "1" ;
     string s2 = "1" + "2"; //錯誤,加号兩邊都是不是string對象
     string s3 = s1 + "1"; //正确,加号左邊是一個string對象
     string s4 = "1" + "2" + s1 ; //錯誤,第一個加号兩邊都不是string對象
     string s5 = s1 + s2; //正确,加号兩邊都是string對象
     return 0 ;
}
           

23、标準庫類型vector表示對象的集合,是一個類模闆

int main()
{
     vector <int > a ;
     vector <vector < int>> b;
     return 0 ;
}
           
【C++筆記】對C++的簡單總結(一)

vector對象能夠高效的增長,是以在定義的時候設定大小是沒有必要的, 甚至性能更差。但是如果所有初始元素都是一樣的值的話是可以在定義的時候初始化的。

int main()
{
     vector <int > vInt = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
     for ( auto &i : vInt )
     {
          i *= i;
     }
     return 0 ;
}
//與string類型一樣,若要改變元素的值,需要在循環的時候對循環變量定義為引用類型
           

24、疊代器

int main()
{
     string s = "asdfasdfasfd" ;
     auto a = s .begin (); //a表示第一個元素
     auto b = s .end (); //b表示尾元素的下一個元素
     cout << *a << endl;//列印結果是輸出'a'
     cout << *b << endl;//錯誤,因為b是指向尾元素的下一個元素,沒有實際指向一個元素,是以不能對他進行解引用或者遞增操作

     vector <int > vInt ( 10, 1 );
     auto c = vInt .begin ();
     auto d = vInt .end ();
     return 0 ;
}
           
容器運算符
*iter               傳回該疊代器所指元素的引用
iter->men           傳回該疊代器所指元素中名為men的成員,可寫成(*iter).men;
++iter              指向該疊代器的下一個元素
--iter              指向該疊代器的上一個元素
iter1 == iter2      判斷兩個疊代器是否相等,如果兩個疊代器指向的是同一個元素或者兩個疊代器都指向同一個容器的尾後疊代器,說明相等;反之不相等。
iter1 != iter2
           

對于疊代器和vector對象的使用上的 注意事項: 1、vector對象可以動态的增長,是以這也限制了使用for(rangefor)來循環對vector中push_back元素。 2、另一個就是:如果定義了一個vector的疊代器,之後再向vector對象中push_back元素的話,原疊代器會失效。

int main()
{
     vector <string > vStr;
     auto a = vStr.begin ();//初始化之後立即定義一個疊代器

     vStr.push_back ( "11" );
     vStr.push_back ( "22" );

     auto b = vStr.begin ();//push_back元素之後重新等一個疊代器

     cout << *a << endl;//報錯,因為vStr為空,a是指向尾疊代器
     cout << *b << endl;//成功,b是指向第一個元素
     return 0 ;
}
           

25、字元數組有一種額外的初始化方式。

int main()
{
     char a[] = { 'c' , '+' , '+' , '\n' };
     char b[] = "c++";
     char c[4] = "c++";
     //這3種初始化方式效果是一樣的
     return 0 ;
}
           

26、指針也可以實作疊代器的功能,但必須得指定頭指針和尾後指針,但不安全,c++11提供了新方法:begin()和end()

int main()
{
     const int size = 10 ;
     int arr[size] = { 0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 };
     int *pHead = arr;
     int *pEnd = &arr[size];//arr[size]這個元素并不存在,是以遞增操作或者也不能解引用來取值,這裡唯一的用處就是提供其位址用于初始化pEnd。
     for ( int *p = pHead ; p != pEnd ; ++p)
          cout << *p << " " ;
     cout << endl;
     return 0 ;
}
           

27、c風格的字元串操作

int main()
{
     char a [] = { '1' , '2' };
     cout << strlen( a ) << endl ;//錯誤,沒有以空字元結束

     char b[] = "11111 22222";
     char d[] = "22222 11111";
     strcmp(b, d);//如果b>d傳回真值,如果b<d傳回負值

     char s[100] = {}; //初始化一個用于存放結果的數組
     strcpy(s, d);//把d複制給s
     strcat(s, d);//把d加在s的後面

     char *p = s;
     while ( *p )
     {
          cout << *p << endl ;//輸出操作結果
          p++;
     }
     return 0 ;
}
//對于大多數應用程式來說使用标準庫string比c風格字元串更安全高效
           
//string對象轉換為c風格字元串
int main()
{
     string s = "11111111" ;
     const char *cc = s .c_str();
     return 0 ;
}
//使用數組初始化vector對象
int main()
{
     int arr[] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 };
     vector <int> vInt1 ( begin( arr ), end (arr) );//把數組的值全部用于初始化vector對象

     //也可以用數組的一部分值來初始化
     vector <int> vInt2 ( arr + 1, arr + 3 );
     vector <int> vInt3 ( begin(arr) + 1, end(arr) - 1);
     
     return 0 ;
}
           

28、用指針來周遊多元數組。

int main()
{
     int arr[3][4] = {
          1, 2, 3, 4,
          5, 4, 3, 2,
          1, 13, 3, 4
          };
     for ( auto p = arr ; p != arr + 3 ; ++p )//先循環一維得到一維的位址
          for ( auto d = *p; d != *p + 4; ++d )//根據一維的位址得到二維位址,并輸出值
               cout << *d << " " ;
     return 0 ;
}
//也可以使用标準函數begin()和end()來實作相同功能,更簡潔安全
int main()
{
     int arr[3][4] = {
            1 , 2 , 3 , 4 ,
            5 , 4 , 3 , 2 ,
            1 , 13 , 3 , 4
            };
     for ( auto p = begin (arr ); p != end (arr ); ++ p )
          for ( auto q = begin (*p ); q != end (*p );++ q)
               cout << *q << " " ;
     return 0 ;
}
           

29、關于遞增遞減運算符的選用

int i = 0; j = 0;
j = ++i;//前置版本,i = 1; j = 1 //改變i的值并傳回給j
j = i++;//後置版本,i = 2; j = 1 //存儲i的值并計算傳回j

//盡量選用前置版本的遞增遞減運算符:
//1、後置版本的需要将原始值儲存下來以便傳回這個未修改的内容,如果我們不需要這個未修改的内容,那麼這個後置版本的操作就是一種浪費。
//2、基于上面的原因,對于整數和指針類型來說,編譯器可能會對這種工作進行一定的優化,但對于相對複雜的疊代器類型來說,這種額外的工作是非常耗性能的。
           

30、sizeof運算符傳回一條表達式或者一個類型名字所占的位元組數,它不會求實際類型的值

sizeof(type)
sizeof expr//傳回表達式結果類型的大小
int main()
{
     string s , * p ;

     sizeof( string );//傳回string類型的對象的大小
     sizeof s;//s類型的大小,與上面一樣
     sizeof p;//指針所占空間大小
     sizeof *p;//因為sizeof不會求實際類型的值,是以即使p沒有初始化也沒有影響
     return 0 ;
}
//對string和vector對象執行sizeof運算隻傳回該類型固定部分的大小,不會計算對象中的元素占用了多少空間
           

31、算術轉換的規則是運算符的運算對象轉換成最寬的類型,如:       1、一個運算類型是long double,那麼不論另一個運算類型是什麼都會轉換為long double。       2、當表達式中有浮點型也有整型時,整數類型将轉換為浮點型。 32、顯示轉換

1、舊版的強制類型轉換
     type (expr)//函數形式的
     type expr//c語言風格的
int main()
{
     int i = 11 , j = 2;
     double res1 = double (i ) / j;

     return 0 ;
}

2、新版的強制類型轉換
     cast-name<type>(expr)
cast-name分别有:static_cast、dynamic_cast、const_cast和reinterpret_cast
static_cast:任何具有明确定義的類型轉換,隻要不包含底層const,都可以使用它。
dynamic_cast:支援運算時類型識别。
const_cast:隻能改變運算對象的底層const。
reinterpret_cast:通常為運算對象的位模式提供較低層次上的重新解釋。
int main()
{
     int i = 11 , j = 2;

//使用static_cast
     double res = static_cast <double> (i) / j ;

     void *d = & i;
     int *p = static_cast <int*> (d);

//使用const_cast
     const char *pc ;
     char *cp = const_cast <char*> (pc);

//使用reinterpret_cast
     int *pi;
     char *ic = reinterpret_cast <char*> (pi);
     string str = ic;//錯誤,ic實際存放的是指向int類型的指針

     return 0;
}
           

33、局部靜态對象:如果我們想讓一個局部變量的生命周期貫穿函數以及之後的時間。我們可以把變量定義為static類型,局部靜态對象在程式執行第一次經過對象時被初始化,直到程式運作終止時才銷毀。

int count()
{
     static int res = 0 ;
     return ++res ;
}

int main()
{
     for ( int i = 0 ; i < 10; ++i )
          cout << count() << endl;
     return 0 ;
}
           

34、建議變量和函數的聲明放在頭檔案中,在源檔案中定義。 35、在拷貝大的類類型對象或者容器對象時是比較低效的。而且有些類類型對象還不支援拷貝。是以使用引用是非常明智的選擇。

//使用引用來比較
bool isShorter( string &s1, string &s2 )
{
     return s1.size() < s2.size();
}
//使用拷貝來比較
bool isShorter( string s1, string s2 )
{
     return s1.size() < s2.size ();
}
//使用常量引用來比較,如果函數不用修改形參的值,最好使用常量引用
bool isShorter( const string &s1, const string &s2 )
{
     return s1.size() < s2.size();
}

int main()
{
     string s1 = "111111" ;
     string s2 = "11111111" ;
     cout << isShorter( s1, s2 ) << endl;
     return 0;
}
           

36、函數的數組形參,數組有兩個特性:(1)不允許拷貝數組。(2)使用數組時會轉為指針

void set(const *int);
void set(const int[]);
void set(const int[20]);//這裡的20表示期望含有多少元素,實際上不一定。
//上面3中表達方式是等價的,都是const *int類型的形參

int i = 0;
int j[2] = {1, 2};
set( &i );//正确
set( j );//正确
           

37、函數中管理指針的3中方法

第一種方法:必須要求數組本身包含結束标記

void set( const char *p )
{
     if ( p )
          while (* p )
          {
               cout << *p ++ << endl;
          }
}

void set(const int *p)
{
     if(p)
     {
          while(*p)
               cout << *p << endl;
     }
}
//這用方法用于有結束标記的情況如char類型。如果用于int類型就會無效。

第二種方法:是傳遞一個數組的首元素和尾後元素的指針
void print( const int *begin , const int *end)
{
     while ( begin != end)
     {
          cout << *begin ++ << endl;
     }
}

int main()
{
     int i[] = { 1, 2 , 3 };
     print (begin (i), end(i));
     return 0 ;
}

第三種方法:是在函數的形參中添加一個表示資料大小的形參,在以前c和c++的程式中經常使用這種方法
void print( const int *p , const int size )
{
     for ( int i = 0 ; i < size; ++i )
     {
          cout << *(p + i ) << endl ;
     }
}
int main()
{
     int i [] = { 1 , 2 , 3 };
     print (i , end ( i) - begin (i ));
     return 0 ;
}
           

38、多元數組就是數組的數組

//p指向數組的首元素,該元素是包含10個整數的數組
void set(int (*p)[10], int rowSize){}
//等價于
void set(int p[][10], int rowSize){}
           

39、傳回引用的函數

char & get_value (string &s , int index)
{
return s [ index] ;
}

int main()
{
     string s = "11111" ;
     get_value (s , 0 ) = '2';
     cout << s << endl ;//輸出21111
     return 0 ;
}
           

40、傳回數組指針的函數

func(int i)//表示調用func時需要一個int型實參
(*func(int i))//表示可以對該函數的結果解引用
(*func(int i))[10]//表示解引用該函數将得到一個大小為10的數組
int (*func(int i))[10]//表示數組中的元素是int類型
           

41、函數指針:函數指針指向的是函數,函數類型由它的傳回值類型和形參類型共同決定。

如:bool compare( const string & , const string &);
則該函數的類型是bool( const string &, const string &),
則指向該函數的指針是:bool (*p)( const string &, const string &)

//函數的重載指針
void set( int *);
void set(char *);

void (*p)( int * ) = set; //p預設指向了 set(int *)

//聲明語句直接使用函數指針顯得冗長,可以使用typedef來簡化。

//下面四個是等價聲明
typedef void func1(int *); //func是函數類型
typedef void (*funcp1)(int *);//funcp是函數指針
using func2 = void( int *);
using funcp2 = void(*)(int *);

使用的時候直接:
void use(int a, int b, func);
void use(int a, int b, funcp);

//用容器把函數存儲起來
int add( const int & a , const int & b )
{
     return a + b ;
}

int reduce( const int & a , const int &b )
{
     return a - b ;
}
int   multiply( const int &a , const int & b)
{
     return a * b ;
}
int divide( const int &a , const int & b)
{
     return a / b ;
}

using p = int (*)( const int &, const int &);

vector < p> b{ add, reduce, multiply, divide } ;

int main()
{
     for ( auto a : b )
     {
          cout << a( 1, 2) << endl ;
     }
     return 0 ;
}
           

42、如果一個類允許其他類或者函數通路它的非公有成員,可以讓這個其他類或者函數成為他們的 友元。

class Person
{
     friend istream & read ( istream& is , Person& person );//友元的聲明僅僅指定了通路權限
private :
     string name;
     string address;
};
inline istream& read( istream& is, Person& person)
{
     is >> person.name >> person.address;//雖然name和address是私有變量,但是成為友元之後可以通路。
     return is;
}
           

43、如果我們希望能修改類的某個資料成員,即使在一個const成員函數中,我們可以通過在資料成員的聲明前加上mutable關鍵字

class Person
{
public :
     void Total () const;
private :
     mutable int sum;
};

void Person:: Total () const
{
++sum;
}
           

44、要想讓某個成員函數作為友元,必須按順序來設計程式。以滿足聲明和定義的依賴關系。

1.首先定義Manager類,其中聲明clear函數,但不定義它。在clear使用Screen之前必須先聲明Screen
2.然後定義Screen,包括對clear友元聲明
3.最後定義clear,這樣才能正常使用Screen的成員。
           
</pre><pre code_snippet_id="1708766" snippet_file_name="blog_20160605_46_4473679" name="code" class="cpp"><pre name="code" class="cpp">class Screen;

class Manager
{
public :
     using ScreenIndex = vector < string>:: size_type ;
     inline void clear( ScreenIndex );
private :
     vector <Screen > screens ;
};

class Screen
{
     friend void Manager:: clear (ScreenIndex );
     //friend class Manager;

private :
     pos width = 0 ;
     pos height = 0 ;
     pos cursor = 0 ;
     string contents ;
};
inline void Manager::clear ( ScreenIndex index )
{
     if ( index > screens.size()) return;
     Screen & sc = screens[index];
     sc.contents = string( sc.width * sc.height, ' ' );
}
           

45、當在類的外部定義成員函數時,必須提供類名和函數名

void Manager::clear ( ScreenIndex index )//因為提供了類名,是以我們可以直接使用類中其他的成員:ScreenIndex、screens等
{
     if ( index > screens.size()) return;
     Screen & sc = screens[index];
     sc.contents = string( sc.width * sc.height, ' ' );
}

//但如果是在函數傳回值要使用類中的成員,就必須指明這個傳回值是屬于哪個類的
Screen::pos Screen::size() cosnst
{
      return height * width;
}
           

46、如果類的成員是const、引用、或者屬于某種沒有提供預設構造函數的類類型,我們必須通過構造函數初始值清單為這些成員提供初值。

class Test
{
public :
     //這個版本的構造函數是對資料成員執行指派操作
     Test (int a, int b , int c )
     {
          val1 = a; //正确
          val2 = b; //錯誤,不能給const指派
          val3 = c; //錯誤,val3沒有初始值
     }
     //下面這個版本是顯式的初始化引用和const,是正确做法
     //Test(int a, int b, int c) :val1(a), val2(b), val3(c){}

private:
     int val1;
     const int val2;
     int &val3;
};
//最好使用第二個版本
           

47、類成員的初始化順序是由它們在類中定義的順序決定的

class Test
{
     int i;
     int j;
public:
     Test(int val ) : j(val),i(j){} //這裡i會先初始化,j後初始化,這樣初始化的結果是用一個未定義的j來初始化i,這種做法是錯誤的,我們應該盡量避免這種情況。
}
           

48、聲明一個用預設構造函數初始化的對象常犯的錯

int main()
{                
     Person person1(); //聲明并初始化一個函數,不是對象
     Person person2; //定義了一個對象,并使用預設構造函數初始化
     return 0 ;
}
           

49、C++中的隐式類類型轉換:如果構造函數隻接受一個參數,那麼它們就預設定義了這個類類型的隐式轉換機制。

int main()
{
     Sales_Data item1 ;
     string newBook = "11111" ;
     item1.combine(newBook);//合法的,這裡的combine實際上是一個接受Sales_Data類型的函數,但在這裡卻接受了一個string類型:它是先建構一個臨時的Sales_Data類型的對象,然後把newBook作為構造函數的參數傳給這個臨時的Sales_Data對象,然後再把這個臨時對象傳給combine
     Sales_Data item2   = "111111" ;//正确,拷貝初始化
     

     item1.combine("11111");//錯誤的,是因為隻允許一次類類型轉換,而這裡是先把字元串轉換成string,然後再把這個臨時的string轉換成Sales_Data對象,執行了二次類類型轉換,是以是錯誤的。
     return 0 ;
}
//如果我們想禁止隐式類類型轉換,可以在構造函數的聲明出使用explicit關鍵字
explicit Sales_Data ( const string s ) : bookId (s ){}
explicit Sales_Data ( istream& is );
           

繼續閱讀