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
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" ) ;
}
//若使用=來初始化則表示拷貝初始化,不使用=初始化表示直接初始化
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 a ;
vector > b;
return 0 ;
}
vector對象能夠高效的增長,所以在定義的時候設定大小是沒有必要的, 甚至性能更差。但是如果所有初始元素都是一樣的值的話是可以在定義的時候初始化的。
int main()
{
vector 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 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 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
//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 vInt1 ( begin( arr ), end (arr) );//把數組的值全部用於初始化vector對象
//也可以用數組的一部分值來初始化
vector vInt2 ( arr + 1, arr + 3 );
vector 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(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 (i) / j ;
void *d = & i;
int *p = static_cast (d);
//使用const_cast
const char *pc ;
char *cp = const_cast (pc);
//使用reinterpret_cast
int *pi;
char *ic = reinterpret_cast (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的成員。
class Screen; class Manager { public : using ScreenIndex = vector < string>:: size_type ; inline void clear( ScreenIndex ); private : vector 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 );