本章知識點總結:
1. 我們在定義一個類時,可以把關於類的聲明寫入一個頭文件中,然後具體的實現過程需要在單獨的cpp文件中完成,需要包含剛纔定義的頭文件,當使用頭文件中聲明的函數名或者變量時要加入類名作用域,這是目前比較標準的寫法。然後如果我們在聲明一個類的時候,同時定義了成員函數,那麼編譯器會自動將其視爲 inline。 但是如果我們按照標準寫法去實現成員函數時,如果需要指明爲 inline 那麼就需要我們去自己手寫
2. 構造函數作用,初始化成員變量,可以通過在構造函數的代碼段內實現,也可以通過初始化列表進行初始化,與之對應的是虛構函數,虛構函數的寫法是在類名前寫一個 ~ , 析構函數不接受任何參數和返回值,用來對之前申請的資源進行釋放
3.當一個空類被創建時,編譯器會默認幫我們創建構造函數,析構函數,賦值函數和拷貝構造函數,其中賦值函數和拷貝構造函數會存在一些問題,下面通過簡單的示例進行講解
在下面這個例子中設計的注意點:1. 對輸入輸出運算符進行重載時,一定是類外重載,因爲 << 運算符的左側一定是一個ostream對象,右側一定是一個類,但是如果我們進行類內重載的話,參數列表中默認的最左側參數是我們的this指針,這樣就不符合我們的實際使用情況了 2. 下面這個程序會崩潰,不管是Cstu st2(st1); 還是Cstu st2 = st1;都會崩潰,這就是上面提到的關於默認的賦值函數和拷貝構造函數可能出現的問題,由於類中的成員變量是在堆區進行申請空間,然後通過默認的上述兩個函數對st2對象進行初始化的時候,只是單純的st2.age = st1.age st2.matric = st1.matric 這種操作,會導致st1 和st2的成員變量指向同一塊兒空間,然後我們在析構函數中對這塊兒空間進行釋放,所以當st2所在的析構函數被調用後,空間被釋放,但此時st1會試圖操控被釋放的空間從而引發錯誤
#include"stdafx.h"
#include<iostream>
using namespace std;
class Cstu {
private:
int age;
int matric[2] = { 0 };
public:
Cstu(int age) {
this->age = age;
}
~Cstu()
{
delete[]this->matric;
}
friend ostream& operator << (ostream &os, const Cstu &st);
};
// 不進行修改加上const 當返回值爲void時,無法進行連續輸出
ostream& operator << (ostream &os, const Cstu &st) {
os << st.age<<" "<<st.matric[0]<<" "<<st.matric[1];
return os;
}
int main(void) {
Cstu st1(12);
{
//Cstu st2(st1);
Cstu st2 = st1;
cout << st2<<endl;
}
cout << st1 ;
return 0;
}
4. const 和 mutable: 關於const變量的性質大家可以參考https://www.cnblogs.com/xudong-bupt/p/3509567.html
對其中的代碼進行解釋
#include<iostream>
using namespace std;
int main(){
int a1=3; ///non-const data
const int a2=a1;
int * a3 = &a1;
//a4 是一個指針型變量,指向const的int型數據,所以指針可指向不同空間,但是數據不能改變
const int * a4 = &a1;
//對於上一行來說 a4=&a2是正確的,即只是改變指針的指向,但是*a4=5是錯的,因爲這是在修改數據
//a5 是一個const型的指針變量,指向int數據,所以在指針指向不能更改,但是數據可以更改
//*a5=2 正確 a5=&a2錯誤
int * const a5 = &a1;
//解讀如上,a6 a7 都是一個const型的指針變量,均指向const的int型數據
int const * const a6 = &a1;
const int * const a7 = &a1;
return 0;
}
由於在const修飾的成員函數中不能修改任何成員變量,但是如果我們在成員變量的定義前加上mutable 表示對該變量所做的修改不會破壞類的常量性
5. this指針 第一部分,用來只用函數的調用者,在內部工作過程中,編譯器自動將this指針加到每一個成員函數的參數列表的最左側,這也就是爲什麼我們上面重載 << 的時候要放到類外去進行
6. 靜態成員 static,該類型變量是依託於類存在的,並不是依託於對象,所以所有對象均使用同一份static變量,且static變量一定得去類外初始化,如果一定要在類內初始化的話,應該聲明爲const。如果用static去修飾成員函數,則該成員函數可通類名作用域進行調用,然後需要注意一點就是,在靜態的成員函數中是不能使用非靜態的成員變量的,因爲非靜態成員變量是依託於對象存在的,我們用類名去調用靜態成員函數,哪裏來的對象嘛。
7.在使用函數指針的時候,如果是普通的函數指針,那麼函數名即表示地址,如果是類中的成員函數,則需要用 &來表示地址
8.關於類模板的注意事項
#include "stdafx.h"
#include<iostream>
using namespace std;
template<typename elemType>
class Cstu {
public:
Cstu(elemType elem):a(elem){}
void myshow();
private:
mutable elemType a;
};
//之後我們在Cstu類外,只要遇到類名Cstu,都需要寫<>
//同時template<typename elemType>的作用域只存在於緊挨着的代碼段
template<typename elemType>
void Cstu<elemType>::myshow(){
cout << (*this).a << endl;
}
int main() {
Cstu<int> stu(5);
stu.myshow();
return 0;
}
9. this指針:(1)是指向當前對象的指針 以上面的代碼爲例就是 Cstu* this ,this是一個指針變量,指向Cstu 所以this實際存儲的是一個對象的地址,也就是說只要拿到了對象的地址,就可以也是用 -> 這個符號進行函數調用。同理和普通指針類似,*this 即代表對象本身(2)this在成員函數的開始執行前構造,在成員的執行結束後清除。並且this不是成員變量,我們不能在主函數中訪問this指針 (3)爲什麼我們能在非靜態成員函數內部使用this指針呢,是因爲編譯器默認幫我們把this指針當做參數列表中的第一個參數進行傳入,所以我們才能用 通過代碼進行說明
#include "stdafx.h"
#include<iostream>
using namespace std;
class Cstu {
public:
Cstu(int elem=5):a(elem){}
Cstu* myAdd() {
return this;
}
void showA() {
cout << this->a << " " << (*this).a << endl;
}
private:
int a;
};
int main() {
Cstu stu(5);
Cstu *stu2 = stu.myAdd();
stu2->showA();
return 0;
}
4.1 實現一個類
知識點總結:在頭文件中進行類或者函數的聲明,在源文件中進行具體實現。一般而言,類由兩部分組成,一組公開的操作函數和重載的運算符,另一組是私有的實現細節。這些操作函數和運算符成爲類的成員函數,並代表這個類的公開接口。 另外需要注意的是,按照規範定義一般在單獨的源文件中進行,如果把定義和聲明寫在了一起,編譯器會將其自動視爲內聯函數。關於書中p103的題目,我的做法如下
//Stack.h
#pragma once
#include"stdafx.h"
#include<string>
#include<vector>
using namespace std;
class Stack {
public:
bool push(const string&);
bool pop(string &);
bool peek(string &);
bool empty();
bool full();
bool find(const string&);
int count(const string&);
int size() {
return _stack.size();
}
private:
vector<string> _stack;
};
//Stack.cpp
#include"stdafx.h" 一定要寫在第一行!!!!!
#include<iostream>
#include"Stack.h"
#include<vector>
#include<string>
#include<algorithm>
using namespace std;
bool Stack::empty(){
return _stack.empty();
}
bool Stack::pop(string &elem) {
if (empty()) {
return false;
}
elem = _stack.back();
_stack.pop_back();
return true;
}
inline bool Stack::full() {
return _stack.size() == _stack.max_size();
}
bool Stack::peek(string &elem) {
if (empty()) {
return false;
}
elem = _stack.back();
return true;
}
bool Stack::push(const string &elem) {
if (full()) {
return false;
}
_stack.push_back(elem);
return true;
}
bool Stack::find(const string &elem) {
vector<string>::iterator first = _stack.begin();
vector<string>::iterator last = _stack.end();
for (;first != last;first++) {
if (*first == elem) {
return true;
}
}
return false;
}
int Stack::count(const string &elem) {
int count = 0;
if (!find(elem)) {
return -1;
}
else {
vector<string>::iterator first = _stack.begin();
vector<string>::iterator last = _stack.end();
for (;first != last;first++) {
if (*first == elem) {
count++;
}
}
}
return count;
}
// chapter01.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
#include<iostream>
#include<vector>
#include"Stack.h"
using namespace std;
int main() {
string str("messi"),str2;
Stack s;
s.push(str);
s.push("ronaldo");
s.push("ronaldo");
s.push("xavi");
s.push("xavi");
s.push("xavi");
cout << "s size:" << s.size()<<endl;
s.peek(str2);
cout << "s peek:" << str2 <<endl;
s.pop(str2);
cout << "s size:" << s.size() <<endl;
cout << "s empty:" << s.empty()<<endl;
cout << "s full:" << s.full()<<endl;
cout << "s find:" << s.find("xavi") << endl;
cout << "s find:" << s.find("kaka") << endl;;
cout << "s count" << s.count("xavi") << endl;
cout << "s count" << s.count("kaka") << endl;
return 0;
}
以上結果完全正確,書中的答案給的是把定義和聲明都寫在了頭文件中,但是我把他們分開了.
這裏還涉及到關於關鍵字extern的知識,extern的作用最精髓的就是,在局部範圍聲明出一個全局變量。
https://www.cnblogs.com/mch0dm1n/p/5727667.html
個人見解:
extern可以置於變量或者函數前,以表示變量或者函數的定義在別的文件中,提示編譯器遇到此變量和函數時在其他模塊中尋找其定義。
extern 變量
在一個源文件裏定義了一個數組:char a[6]; 在另外一個文件裏用下列語句進行了聲明:extern char *a; 請問,這樣可以嗎? 答案與分析: 1)、不可以,程序運行時會告訴你非法訪問。原因在於,指向類型T的指針並不等價於類型T的數組。extern char *a聲明的是一個指針變量而不是字符數組,因此與實際的定義不同,從而造成運行時非法訪問。應該將聲明改爲extern char a[ ]。 2)、例子分析如下,如果a[] = "abcd",則外部變量a=0x12345678 (數組的起始地址),而*a是重新定義了一個指針變量a的地址可能是0x87654321,直接使用*a是錯誤的. 3)、這提示我們,在使用extern時候要嚴格對應聲明時的格式,在實際編程中,這樣的錯誤屢見不鮮。 4)、extern用在變量聲明中常常有這樣一個作用,你在*.c文件中聲明瞭一個全局的變量,這個全局的變量如果要被引用,就放在*.h中並用extern來聲明。
函數
extern 函數1 常常見extern放在函數的前面成爲函數聲明的一部分,那麼,C語言的關鍵字extern在函數的聲明中起什麼作用? 答案與分析: 如果函數的聲明中帶有關鍵字extern,僅僅是暗示這個函數可能在別的源文件裏定義,沒有其它作用。即下述兩個函數聲明沒有明顯的區別: extern int f(); 和int f(); 當然,這樣的用處還是有的,就是在程序中取代include “*.h”來聲明函數,在一些複雜的項目中,我比較習慣在所有的函數聲明前添加extern修飾。 extern 函數2 當函數提供方單方面修改函數原型時,如果使用方不知情繼續沿用原來的extern申明,這樣編譯時編譯器不會報錯。但是在運行過程中,因爲少了或者多了輸入參數,往往會照成系統錯誤,這種情況應該如何解決? 答案與分析: 目前業界針對這種情況的處理沒有一個很完美的方案,通常的做法是提供方在自己的xxx_pub.h中提供對外部接口的聲明,然後調用方include該頭文件,從而省去extern這一步。以避免這種錯誤。
習題4.3
// chapter01.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
#include<string>
#include<iostream>
using namespace std;
class globalWrapper {
public:
static void put_program_name(string &name) {
program_name = name;
}
static void put_version_stamp(string &stamp) {
version_stamp = stamp;
}
static void put_version_number(int number) {
version_number = number;
}
static void put_tests_run(int run) {
tests_run = run;
}
static void put_tests_passed(int passed) {
tests_passed = passed;
}
static string get_program_name() {
return program_name;
}
static string get_version_stamp() {
return version_stamp;
}
static int get_version_number() {
int version_number;
}
static int get_tests_run() {
return tests_run;
}
static int get_tests_passed() {
return tests_passed;
}
private:
static string program_name;
static string version_stamp;
static int version_number;
static int tests_run;
static int tests_passed;
};
string globalWrapper::program_name ;
string globalWrapper::version_stamp;
int globalWrapper::version_number;
int globalWrapper::tests_run;
int globalWrapper::tests_passed;
int main() {
//string s("ww");
//globalWrapper::put_program_name(s);
cout << globalWrapper::get_program_name();
return 0;
}
習題4.4
// chapter01.cpp : 定義控制檯應用程序的入口點。
//
#include "stdafx.h"
#include<iostream>
#include<string>
#include<map>
#include<ctime>
using namespace std;
class UserProfile {
public:
enum uLevel
{
Beginner,
Intermediate,
Advanced,
Guru
};
UserProfile();
UserProfile(string login, uLevel = Beginner);
bool operator ==(const UserProfile &);
bool operator !=(const UserProfile &);
//以下函數用來讀取數據
string login() const { return _login; }
string user_name() const { return _user_name; }
int login_count() const { return _time_logged; }
int guess_count() const { return _guesses; }
int guess_correct() const { return _correct_guesses; }
double guess_average() const;
string level() const;
//以下函數用來寫入數據
void reset_login(const string&val) { _login = val; }
void user_name(const string &val) { _user_name = val; }
void reset_level(const string&);
void reset_level(uLevel newlevel) { _user_level = newlevel; }
void reset_login_count(int val) { _time_logged = val; }
void reset_guess_count(int val) { _guesses = val; }
void reset_guess_correct(int val) { _correct_guesses = val; }
void bump_login_count(int cnt = 1) { _time_logged += cnt; }
void bump_guess_count(int cnt = 1) { _guesses += cnt; }
void bump_guess_correct(int cnt = 1) { _correct_guesses += cnt; }
private:
string _login;
string _user_name;
int _time_logged;
int _guesses;
int _correct_guesses;
uLevel _user_level;
static map<string, uLevel> _level_map;
static void init_level_map();
//static string guest_login();
};
inline double UserProfile::guess_average() const {
return _guesses ? (double)_correct_guesses / (double)_guesses * 100 : 0;
}
inline UserProfile::UserProfile(string login, uLevel level):_login(login), _user_level(level),
_time_logged(1),_guesses(0),_correct_guesses(0){}
#include<cstdlib>
inline UserProfile::UserProfile() : _login("guest"), _user_level(Beginner),
_time_logged(1), _guesses(0), _correct_guesses(0) {
static int id = 0;
char buffer[16];
_itoa_s(id++, buffer, 10);
_login += buffer;
}
inline bool UserProfile::operator==(const UserProfile &rhs) {
if (_login == rhs.login() && _user_name == rhs._user_name)
return true;
return false;
}
inline bool UserProfile::operator!=(const UserProfile &rhs) {
return !(*this== rhs);
}
inline string UserProfile::level()const {
static string _level_table[] = { "Beginner","Intermediate","Advanced","Guru" };
return _level_table[_user_level];
}
ostream & operator <<(ostream &os, const UserProfile &rhs) {
os << rhs.login() << " " << rhs.level() << " " << rhs.login_count() << " " << rhs.guess_count() << " "
<< rhs.guess_correct() << " " << rhs.guess_average() << "\n";
return os;
}
map<string, UserProfile::uLevel> UserProfile::_level_map;
void UserProfile::init_level_map() {
_level_map["Beginnner"] = Beginner;
_level_map["Intermediate"] = Intermediate;
_level_map["Advanced"] = Advanced;
_level_map["Guru"] = Guru;
}
inline void UserProfile::reset_level(const string &level) {
map<string, UserProfile::uLevel>::iterator it;
if (_level_map.empty())
init_level_map();
_user_level = ((it = _level_map.find(level)) != _level_map.end()) ? it->second : Beginner;
}
istream & operator >> (istream &is, UserProfile &rhs) {
string login, level;
is >> login >> level;
int lcount, gcount, gcorrect;
is >> lcount >> gcount >> gcorrect;
rhs.reset_login(login);
rhs.reset_level(level);
rhs.reset_guess_count(gcount);
rhs.reset_guess_correct(gcorrect);
rhs.reset_login_count(lcount);
return is;
}
int main() {
srand((unsigned int)(time(NULL)));
UserProfile anon;
cout<<anon;
UserProfile anon_too;
cout << anon_too;
UserProfile anna("Annal", UserProfile::Guru);
cout << anna;
return 0;
}