Heim >Backend-Entwicklung >C#.Net-Tutorial >Denken in C++, Band 1, Lesenotizen, Schlüsselzusammenfassung
In diesem Artikel geht es hauptsächlich darum, einige Notizen aus dem ersten Band von Thinking in C++ zu lesen. Hauptsächlich einige Punkte, die es zu beachten gilt
Denken in C++ Kapitel 2
Übersetzer:
Compilerschritte zum Kompilieren des Programms:
Deklaration und Definition von Funktionen oder Variablen
Verbindung
Denken in C++ Kapitel 3
Funktionsrückgabebeschreibung
Üblich Funktionsbibliothek
Beschreibung der for-Schleife
Schalterbeschreibung
Bezeichner(Bezeichner)
void*-Zeigerbeschreibung
Gültiger Bereich der Variablen
Globale Variable
Statische Variablen
Verbindung (Verknüpfungstyp)
Vorverarbeitungsmakros
typedef-Syntaxbeschreibung
Enum-Beschreibung:
Denken in C++ Kapitel 5
Freunde
Verschachtelte Freunde
andere strukturieren
Mitdenken C++ Kapitel 6
Entsprechende Beschreibung des Konstruktors
lösche void*
Standardkonstruktor
Denken in C++ Kapitel 7
Überlastung
Typ- sichere Verknüpfung
Union
Standardparameterverwendungsregeln:
Platzhalterparameter
Denken in C++ Kapitel 8
const-Beschreibung
const-Zeiger
Zuordnung und Typprüfung
Temporäre Variable
Übergabe- und Rücksendeadresse
Standardparameterübergabe
Klasseninterne Datenelemente werden konstant initialisiert
Konstanten während der Kompilierung
const-Objekte und Mitgliedsfunktionen
Denken in C++ Kapitel 9
Inline-Funktionen
Verstecktes Verhalten von Konstruktoren und Destruktoren
Container vorverarbeiten
Denken in C++ Kapitel 10
statisches
statisches Objekt
interner Link
andere
stactic/const/stctic const
Statische Memberfunktion
Zeiger-Array/Array-Zeiger
Funktionszeiger
Funktionsadresse
Funktionszeiger
Funktionszeiger aufrufen
Array von Funktionszeigern
Typedef vereinfacht den Arbeitsaufwand
Namespace
Namespace
Unbenannter Namespace
Using-Direktive & Using-Deklaration
new/malloc/delete/free
new/ malloc
löschen/freigeben
Global neu überladen und löschen
Klasse überladen neu und löschen
Neu überladen und löschen für Arrays löschen
Neu positionieren/löschen
Vererbung und Zusammensetzung
Initialisierungsausdruck
Die Reihenfolge der Konstruktor- und Destruktoraufrufe
Nicht -Automatisch geerbte Funktionen
Vererbung und statische Mitgliedsfunktionen
Private Vererbung
Upcasting und Kopieren Konstruktor
Operatorüberladung
+=,-=,*=,/=,= und andere Operatorüberladung
Erklärung der Funktionsparameter und Rückgabewerte
Präfix ++ & Postfix ++
Konstanter Rückgabewert und Rückgabewert Optimieren
Operator[]
Operator-> (Zeiger-Dereferenzierungsoperator)
Operator->*
Grundlegende Richtlinien für Operator-Mitgliedsfunktionen
Copy-on-Write
Automatische Typkonvertierung
Interpreter
Compiler
Der Präprozessor verarbeitet Vorverarbeitungsanweisungen
Der erste Durchgang analysiert den Vorverarbeitungscode, um die Anzahl der Knoten zu generieren, die durchgeführt wird, bevor mit dem zweiten Schritt fortgefahren wird Optimierer: Der Codegenerator im zweiten Durchgang analysiert den Codebaum, um Maschinensprache oder Assemblersprache zu generieren
Statische Typprüfung wird im ersten Durchgang durchgeführt
void function();//声明void function(){}//定义extern int a;//声明int a;//定义
In der letzten Phase des Kompilierungsprozesses wird das vom Compiler generierte Zielmodul mit einer ausführbaren Datei verbunden (kann sein). vom Betriebssystem erkannt )
Die Return-Anweisung verlässt die Funktion und kehrt zum Punkt nach dem Funktionsaufruf, Lenovo Stack, zurück Betrieb
von einem Bibliotheksmanager verwaltet, der Objektmodule verwaltet. Dieser Bibliotheksmanager verwaltet .lib/.a-Dateien
for(initialization;conditional;step)
Die for-Schleife führt zunächst die Initialisierung durch und bestimmt dann die Bedingung. Wenn sie erfüllt ist, tritt sie in die Schleife ein. Fahren Sie nach dem Ausführen der Schleife mit Schritt
switch(selector){ case integral-value:statement;break; ... defualt:statement; }
fort Der Selektor im Schalter muss ein ganzzahliger Wert sein, und der ganzzahlige Wert muss sein. Der ganzzahlige Wert
Selektor kann auch ein Wert vom Typ Enum sein
wird verwendet, um die Bedeutung des integrierten Basistyps zu ändern und den Basistyp in einen größeren Satz zu konvertieren
1 short und long, das Schlüsselwort int kann weggelassen werden)
2. float/double: kein long float, nur long double , das heißt: float / double /long double
3 in Ganzzahl- und Zeichentypen)
void*-Zeiger kann als beliebiger Adresstyp zugewiesen werden, aber eine falsche Typkonvertierung führt zum Absturz des Programms
Beginnt vom Definitionspunkt bis zum nächsten Nachbarn, bevor die Variable definiert wird. Die erste schließende Klammer des Paares offener Klammern ist der Gültigkeitsbereich bestimmt durch das letzte Klammerpaar, in dem sich die Variable befindet.
Der Lebenszyklus globaler Variablen dauert bis zum Ende des Programms. Sie können das Schlüsselwort extern verwenden, um globale Variablen in einer anderen Datei zu verwenden
Der Vorteil einer statischen Variablen besteht darin, dass sie nicht außerhalb des Funktionsbereichs verfügbar ist und nicht einfach geändert werden kann, wodurch Fehler lokalisiert werden. Wenn statische Variablen auf den Funktionsnamen und alle externen Variablen angewendet werden Funktion, es bedeutet „in der Datei Dieser Name kann nicht extern verwendet werden“, das heißt, es hat einen Dateibereich wie
//file1.cppstatic int fs;int main(){ fs = 1; }//file2.cppextern int fs;//编译器不会找到file1.cpp文件中的fs,即文件作用域void function(){ fs = 100; }
extern static int i;int main(){ cout << i << endl; }static int i = 0; 将出现error,即全局静态变量声明是有文件作用域的,编译器将会产生错误
interne Verbindung: Nur die zu kompilierende Datei wird gespeichert. Erstellen Sie einen separaten Speicherplatz für jede Kennung. Die interne Verbindung wird durch das Schlüsselwort static
angegeben. Externe Verbindung: Erstellen Sie einen separaten Speicherplatz für alle kompilierten Dateien, d. h. alle Variablen und Funktionen sind in diesem Bereich enthalten
Automatische (lokale) Variablen existieren nur vorübergehend auf dem Stapel, der Linker kennt keine automatischen Variablen, daher sind diese Variablen sind nicht verbunden
#define PRINT(STR,VAR) \ cout << STR << VAR << endl; \ cout << VAR << STR <<endl 即可以像调用函数一样调用PRINT,这里预处理宏分行使用'\',宏只是展开,并替换 #define PRINT(STR,VAR) \ cout << #STR << VAR << endl 这里'#'表示STR字符串化(stringizing),比如:int i = 0;PRINT(i,i); //这里输出应该是 i:0
typedef existent-type-description alias-name
typedef unsinged long ulong;
typedef int* intPtr
typedef struct MyStruct{
//Hier ist das C reguläre Strukturdefinition
} MyStruct;
typedef int (*fun)(int,int) //Der Funktionszeiger-Alias macht Spaß
Die Prüfung der Enum in C++ ist strenger (a ist eine Farbtyp-Enumeration) in C, aber nicht in C++, da a++ zunächst zwei Konvertierungen durchführt. Der Farbtyp wird in int konvertiert und dann um 1 erhöht. Konvertiert den Wert in Farbe und ist bei der zweiten Konvertierung unzulässig
Kann über das Schlüsselwort „friend“ aufgerufen werden. Interne private Mitgliedsvariablen oder Mitgliedsfunktionen
struct X;struct Y{ void f(X*); };struct X{ private: int i; public: void initialize(); friend void g(X*,int); //Global friend friend void Y::f(X*); //struct member friend friend struct z; //Entire struct is a friend friend void h(); }
Deklaration einer verschachtelten Set-StrukturY::f(X*) beziehen sich auf die Adresse eines X Der Compiler weiß, wie eine Adresse übergeben wird. Unabhängig davon, welches Objekt übergeben wird, hat die Adresse eine feste Größe. Wenn er versucht, das gesamte Objekt zu übergeben, muss der Compiler die gesamte Definition von X kennen, um dessen Größe zu bestimmen und wie es übergeben wird , unter Verwendung einer unvollständigen Typspezifikation (unvollständige Typspezifikation), d. h. Deklaration von Struktur Sie können die folgende Methode verwenden, um auf die
const in sz = 20;struct Holder{ private: int a[sz]; public: void initialize(); struct Pointer; friend Pointer; struct Pointer{ private: Holder* h; int* p; public: void initialize(Holder* h); void next(); void previous(); void top(); void end(); int read(); void set(int i); }; };void Holder::initialize(){ memset(a,0,sz*sizeof(int)); }void Holder::Pointer::initialize(Holder* rv){ h = rv; p = rv->a; } ...int main(){ Hodler h; Holder::Pointer hp; int i; h.initialize(); hp.initialize(&h); ... }
//: Hadler.h class Handler{ struct Cheshire; Cheshire* smile; public: ...}; //:~ //:Handler.cpp struct Handler::Cheshire{ int i; ...}...
Entsprechende Beschreibung des Konstruktors
class Object{public: Object(int number = 0);private: int m_number; };//:~//: mainint main(int argc, char *argv[]) { int i = 0; switch(i){ case 0: Object obj1{1}; break; case 1: //error: cannot jump from switch statement to this case label "case 1:" Object obj2{2}; //jump bypasses variable initialization "Object obj1{1};" break; } return 0; }
void* löschen
Wenn void* auf ein nicht integriertes Objekt zeigt, wird nur der Speicher freigegeben und der Destruktor wird nicht ausgeführt默认构造函数
class Object{public: Object(int number);private: int m_number; };Object object[2] = {Object{1}};Object没有默认构造函数,数组声明初始化时将报错,object[1]必须有默认构造函数进行初始化,否则报错当且仅当没有构造函数时编译器会自动创建一个默认构造函数
Thinking in C++ Chapter 7
overload
使用范围和参数可以进行重载
void f();class X{void f();};类型安全连接(type-safe linkage)
1.cppvoid functin(int);2.cppvoid function(char);int main(){ function(1); //cause a linker error; return 0; }编译成功,在C中连接成功,但是在C++中连接出错,这是C++中的一种机制:类型安全连接
Union
class SuperVar{ enum{ character, integer, floating_point } vartype; union{ char c; int i; float f; }; public: SuperVal(char ch); SuperVal(int ii); SuperVal(float ff); void print(); }; SuperVal::SuperVali(char ch){ vartype = character; c = ch; } SuperVal::SuperVali(int ii){ vartype = integer; i = ii; } SuperVal::SuperVali(float ff){ vartype = floating_type; f = ff; }void SuperVal::print(){ switch(vartype){ case character: cout << "character:" << c <<endl; break; case integer: cout << "integer :" << i <<endl; break; case floating_point: cout << "float :" << f <<endl; break; } }int main(){ SuperVar A('c'),B(12),C(1.44f); A.print(); B.print(); C.print(); return 0; }
enum没有类型名,因为后面没有必要涉及美剧的类型名称,所以枚举类型名可选,非必须
union没有类型名和标识符,称为匿名联合(anonymous union),不需要使用标识符和以点操作符方式访问这个union的元素
访问一个匿名联合成员就像访问普通变量一样,唯一区别在于:该联合的两个变量占用同一内存空间,如果匿名union在文件作用域内(在所有函数和类之外),则它必须声明为static,以使它有内部的连接
默认参数使用规则:
只有参数列表的后部参数才可以是默认的
一旦在一个函数调用中开始使用默认参数,那么这个参数后面的所有参数都必须为默认的
占位符参数
void f(int i, int = 0, float = 1.1); //version 1void f(int i ,int , float flt); // version 2其中version 2除了i,flt之外中间参数就是占位符参数
Thinking in C++ Chapter 8
const说明
C++中const默认为内部连接,仅在const被定义的文件中才可见,在连接时不能被其它编译单元看见,当定义一个const时必须赋值给它,除非使用extern进行说明
extern const int bufsize;
通常c++不为const创建空间,将其定义保存在符号表内,但是上面的extern进行了强制内存空间分配,另外如取const的地址也是需要存储空间的分配。
对于复杂的结构,编译器建立存储,阻止常量折叠。在C中const默认为外部连接,C++默认为内部连接.出现在所有函数外部的const作用域是整个文件,默认为内部连接
const指针
const修饰指针正指向的对象 const int* a;
const修饰在指针中的地址 int* const a;
赋值和类型检查
const对象地址不可以赋值给一个非const指针,但是可以吧一个非const对象地址赋值给一个const指针
字符数据的字面值:
char * cp = "howdy";char cp[] = "howdy";指针cp指向一个常量值,即常量字符数组,数组cp的写法允许对howdy进行修改
临时变量
在求表达式值期间,编译器必须创建零时变量,编译器为所有的临时变量自动生成为const
传递和返回地址
void t(int*) {}void u(const int* clip){ //*clip = 2; error int i = *clip; //int * ip2 = clip error;}const char* v(){ return "result of functin 0"; }const int * const w(){ static int i; return &i; }int main(){ int x= 0; int * ip = &x; const int * cip = &x; t(ip); //ok //t(cip); not ok; u(ip); //ok u(cip);//ok //char * cp = v(); not ok const char* ccp = v();//ok //int * ip2 = w(); not ok const int * const ccip = w();// ok const int* cip2 = w();//ok //*w() = 1; not ok}
const指针不可以赋值给非const指针,但是非const指针可以赋值给const指针
函数v()返回一个从字符数组的字面值中建立的const char *,在编译器建立了它并把它存储在静态存储区之后,该声明实际上产生该字符数组的字面值的地址
函数w()返回值要求这个指针以及这个指针所指向的对象均为常量,与函数v()类似,因为i是静态的,所以函数返回后返回值仍然有效
const int* const w()只有在作左值时第二个const才能显现作用,所以w()返回值可以赋值给const int *
标准参数传递
可以将临时对象传递给const引用,但不能将一个临时对象传递给接收指针的函数,对于指针必须明确接受地址(临时变量总是const)
class X {public: X() {} }; X f(){return X();}void g1(X&){ }void g2(const X&){ }int main(int argc, char *argv[]) { g1(f()); //error! g2(f()); return 0; }类内数据成员为const初始化
必须在构造函数的初始化列表中进行初始化
编译期间的常量
一个内建类型的static const可以看成编译期间的常量,但是该static const必须在定义的地方进行初始化
无标记enum也可以看成为编译期间常量,一个枚举在编译期间必须有值
class X{ enum {size = 1000}; //same as static const static const int size = 1000; int i[size]; }const对象和成员函数
若将一个成员函数声明为const,则该成员函数可以被const对象调用
const成员函数可以调用非const成员和const成员,非const成员函数同样可以使用const成员
const对象只能调用const成员函数,非const对象调用非const成员函数
Thinking in C++ Chapter 9
内联函数
内联函数与普通函数一样执行,但是内联函数在适当的地方像宏一样展开,不需要函数调用的开销(压栈,出栈),任何在类内部定义的函数自动成为内联函数
内联函数体过大时,编译器将放弃使用内联
当取函数地址时,编译器也将放弃内联
一个内联函数在类中向前引用一个还没有声明的函数时,是可以的,因为C++规定只有在类声明结束后,其中的内联函数才会被计算
class Forward{ int i; public: Forward():i(0){} int f() const {return g()+i;} int g() const {return i;} }构造函数和析构函数隐藏行为
class X { int i,j,k;public: X(int x = 0):i(x),j(x),k(x) { cout << "X" <<endl;} //X(int x):i(x),j(x),k(x){cout << "X" <<endl;} ~X(){cout << "~X" <<endl;} };class Y{ X q,r,s; int i;public: Y(int ii):i(ii){cout << "Y" <<endl;} ~Y(){ cout << "~Y" <<endl; } };int main(int argc, char *argv[]) { Y y(1); return 0; }//:~//:outputX X X Y ~Y ~X ~X ~X
类中包含子对象,构造函数先调用子对象的构造函数,如果没有默认构造函数,则必须在初始化列表中进行初始化,然后在调用类的构造函数
类中包含子对象,先调用类的析构函数在调用子类的析构函数
预处理器
1. #define DEBUG(x) cout << #x "=" << x<<endl;#:字符串化,详见REF2. #define FIELD(a) char* a##_string;int a##_size##:标志粘贴,允许设两个标识符并将它们粘贴在一起产生新的标识符Thinking in C++ Chapter 10
static
在固定地址上进行存储分配,在一个特殊的静态数据区上创建,不是在堆栈上产生
对一个特定编译单位来说是局部的,static可以控制名字的可见性,该名字在这个单元或者类以外是不可见的
static对象
如果没有为一个内建类型的静态变量提供一个初始化值,编译器会确保在程序开始时它被初始化为零(转化成适当的类型)
如果在定义一个静态对象时没有指定构造参数时,该类必须有默认的构造函数
内部链接
常量、内联函数默认情况下为内部链接
others
所有全局对象隐含为静态存储
对static函数意味着只在本单元可见,成为文件静态(file static)
stactic/const/stctic const
static成员必须在类外初始化,如果不初始化,则编译器不会进行默认初始化,对于非内建类型,可以使用构造函数初始化代替“=”操作符
类内const成员必须在构造函数的初始化列表中进行初始化
static const变量(内建类型)必须在声明的地方就初始化
static对象数组,包括const和非const数组必须在类外部初始化
自定义class类声明为stctic,不管其为const或者非const都必须在类外初始化
类的静态成员必须进行初始化后才可以使用
静态成员函数
类的静态成员函数不能访问一般数据成员或者函数,只能访问静态数据成员,也能调用其他静态成员函数
静态成员函数没有this指针
指针数组/数组指针
优先级低的先读
*p[] 指针数组
(*p)[] 数组指针,即指向一个数组函数指针
函数地址(Function Address)
函数的地址:函数名后不跟参数
void fun(){}fun即为函数地址fun()为函数的调用函数指针
double pam(int); //prototypedouble (*pf)(int); //function pointerpf=pam;//pf now points to the pam();调用函数指针
double x=pf(5);double x=(*pf)(5);函数指针数组
const double* f1(const double ar[],int n);const double* f2(const double [],int);const double* f3(coanst double *,int);//f1,f2,f3函数声明本质一样const double* (*pa[3])(const double * , int) = {f1,f2,f3}; //[]优先级高于*,所以表示pa是个数组,数组中包含三个指针auto pb = pa;const double * px = pa[0](av,3);const double * py = (*pb[1])(av,3);double x = *pa[0](av,3);double y = *(pb[1])(av,3);指向整个数组的指针,即是一个指针,而不是一个数组,优先级低的先读
*p[] 指针数组
(*p)[] 数组指针,即指向一个数组const double* (*(*pd)[3])(const double* , int) = &pa;->等价形式 auto pd = &pa;pd指向数组,*pd就是数组,而(*pd)[i]是数组中的元素,即函数指针
函数调用:(*pd)[i](av,3)->此处返回const double * *(*pd)[i](av,3)->此处返回 double 另外一种函数调用略复杂(*(*pd)[i])(av,3)->返回const double * *(*(*pd)[i])(av,3)->返回 doubletypedef简化工作量
typedef const double* (*p_fun)(const double* ,int); p_fun p1 = f1; p_fun pa[3] = {f1,f2,f3}; p_fun (*pd)[3] = &pa;namespace
namespace
namespace只能在全局范围内定义,但是可以相互嵌套
在namespace定义的结尾,右花括号后面不必跟一个分号
可以按类的语法来定义一个namespace,定义的内容可以在多个头文件中延续,就好像重复定义这个namespace
//:header1.h namespace MyLib{ extern int x; void f(); //...} //:~ //:header2.h namespace MyLib{ extern int y; void g(); //...}
一个namespace的名字可以用另一个名字来作为它的别名
namespace lib = MyLib;不能像类一样创建一个名字空间的实例
未命名的名字空间
namespace { class A{}; class B{}; int i,j,k; //...}将局部名字放在一个未命名的名字空间中,不需要加上static就可以作为内部连接
using directive& using declaration
using directive:using namespace xxusing declaration:using xx::f;new/malloc/delete/free
new/malloc
new计算内存大小,并调用构造函数,malloc需要手工计算内存大小,不调用构造函数
delete/free
delete先执行析构函数,在清空内存,free直接清空内存
delete用于void*时将不会调用析构函数,直接清空内存重载全局new和delete
重载的new必须有一个size_t参数,该参数由编译器产生并传递给我们,分配内存的长度,必须返回一个指向等于该长度的对象的指针,如果没有找到存储单元,则返回一个0,然而如果找不到存储单元,不能仅仅返回0,还应该有new-handler或产生一个异常信息
new返回void*,而不是指向任何特定类型的指针,只需要完成内存分配,而不是完成对象的创建,直到构造函数调用才能完成对象的创建,调用构造函数是编译器完成的
delete参数是由new分配的void*指针,该参数是在调用析构函数后得到的指针,析构函数从存储单元中移去对象
#include <cstdio>#include <cstdlib>using namespace std;void* operator new(size_t sz){ printf("operator new:%d Bytes\n",sz); void* m = malloc(sz); if(!m) puts("out of memry"); return m; }void operator delete(void* m){ puts("operator delete"); free(m); }class S{ int i[100];public: S(){puts("S::S()");} ~S(){puts("S::~S()");} };int main(int argc, char *argv[]) { int * p = new int(47); delete p; S* s = new S; delete s; S* sa = new S[3]; delete [] sa; return 0; }//:outputoperator new:4 Bytesoperator deleteoperator new:400 Bytes S::S() S::~S()operator deleteoperator new:1208 Bytes S::S() S::S() S::S() S::~S() S::~S() S::~S()operator delete这里使用printf(),puts()等函数,而不是iostreams,因为使用iostreams对象时(全局对象cin,cout,cerr),调用new分配内存,printf不会进入死锁状态,它不调用new来初始化自身
类重载new和delete
//p328
主要思想:使用static数组以及一个bool数组,返回static数组的下标地址,进行new,得到static下标数组进行delete
void* operator new(size_t) throw(bad_alloc);
void operator delete(void*);为数组重载new和delete
//p331
定位new/delete
//p333
主要思想:使用new运算符重载,但是不对delete运算符重载,指定内存位置new
void* operator new(size_t,void*);继承与组合(Inheritance&Composition)
初始化表达式
成员对象如果没有默认的构造函数,必须显示的进行初始化,即在构造函数初始化列表中进行初始化
只有执行成员类型初始化之后,才会进入构造函数
没有对所有成员以及基类对象的构造函数调用之前,若基类没有默认构造函数则必须初始化,即在初始化列表中进行初始化,否则无法进入构造函数体
构造函数和析构函数调用的顺序
构造函数调用的顺序:首先调用基类构造函数,然后调用成员对象的构造函数,调用成员对象构造函数的顺序是按照成员对象在类中声明的顺序执行,最后调用自己的构造函数
析构函数调用次序与构造函数调用次序相反,先调用自己的析构函数,在调用成员函数的析构函数,最后调用基类析构函数
对于多重继承,构造函数调用顺序为继承时的声明顺序
非自动继承的函数
构造函数
析构函数
operator=
继承与静态成员函数
静态成员函数与非静态成员函数的共同特点:
1. 均可以被继承到派生类中
2. 重新定义一个静态成员,所有基类中的其他重载函数会被隐藏
3. 如果我们改变了基类中的函数的特征,所有使用该函数名字的基类版本将会被隐藏。
4. 静态成员函数不可以是虚函数私有继承
使用私有继承是为了不允许该对象的处理像一个基类对象,一般private更适合于组合
私有继承成员公有化
class Base{ public: Base(){} ~Base(){} void name() {cout << "Base name" <<endl;} } class Derived:private Base{ public: Derived(){} ~Derived(){} using Base::name; //私有继承公有化}向上类型转换和拷贝构造函数
class Base{ public: Base(){} Base(const Base&){} ~Base(){} void name() {cout << "Base name" <<endl;} } class Derived:public Base{ public: Derived(){} Derived(const Derived& d):Base(d){ }//这里是调用基类的拷贝构造函数 ~Derived(){} }基类拷贝构造函数的调用将一个Derived引用向上类型转换成一个Base引用,并且使用它来执行拷贝构造函数,向上类型转换是安全的
运算符重载
+=,-=,*=,/=,=等一类运算符重载
首先进行自我检查(是否对自身赋值),即this == &val,在进行运算,“=”运算符只允许作为成员函数进行重载
函数参数和返回值说明
对于任何函数参数,如果仅需要从参数中读而不改变它,默认地应当做const引用传递。普通算数运算符(像“+”,“-”)和bool运算符不会改变参数,所以const引用为主要传递方式,当函数时成员函数时,就转换为const成员函数。只有会改变左侧参数的运算符赋值(如+=)和operator=,左侧参数不是常量,但因参数将被改变,所以参数仍然按地址传递
返回值类型取决于运算符的具体含义,如果使用该运算符产生一个新值,就需要产生一个作为返回对象的新对象。例如operator+必须生成一个操作数之和的对象,该对象作为一个常量通过返回值返回,所以作为一个左值不会被改变
所有赋值运算符均改变左值,为了使赋值结果能用于链式表达式(如a=b=c),应该能够返回一个刚刚改变了的左值的引用。但该引用并非一定要是常量引用,如(a=b).f(),这里b赋值给a,a调用成员函数f,因此所有赋值运算符的返回值对于左值应该是非常量引用(如果是常量引用,则f成员函数必须为const函数,否则无法调用该函数,这与愿望相违背)
对于逻辑运算符,人们至少希望得到一个int返回值,或者最好是bool值
Prefix ++ & Postfix ++
成员函数
const Object& operator++(){}const Object operator++(int){}
友元函数
const Object& operator++(Object& obj){}const Object operator++(Object& obj,int){}对于友元函数重载来说,因为传入的Object对象被改变,所以使用非常量引用
前缀通过引用返回,后缀通过值(临时对象)返回,因为后缀返回临时对象,所以后缀通过常量值返回,前缀返回引用,如果希望可以继续改变对象则返回引用,否则通过常量引用返回比较合适,这样与后缀保持了一致性常量传值返回与返回值优化
作为常量通常通过传值方式返回。考虑二元运算符+,假设在表达式f(a+b)中使用,a+b的结果变为一个临时对象(Object),该对象被f()调用,因为它为临时的,所以自动被定义为常量,所以无论是否返回值为常量都没有关系。但是如果使用(a+b).f(),这里设返回值为常量规定了对于返回值只有常量成员函数才可以调用
返回值优化通过传值方式返回要创建的的新对象时,注意使用的形式,如operator+
version 1:return Object(lObj.i+rObj.i); version 2: Object tmp(lObj.i+rObj.i);return tmp;version 2将会发生三件事,首先创建tmp对象,然后调用拷贝构造函数把tmp拷贝到外部返回值的存储单元中,最后当tmp在作用域的结尾时调用析构函数
version 1编译器直接将该对象创建在外部返回值的内存单元,不是整的创建一个局部变量所以仅需要一个普通的构造函数调用(不需要拷贝构造函数),且不会调用析构函数,效率高。这种方式被称为返回值优化。operator[]
该运算符必须是成员函数,而且只接受一个参数,可以返回一个引用,可以用于等号左侧。
operator->(指针间接引用运算符)
该运算符一定是一个成员函数,它必须返回一个对象(或者引用),该对象也有一个指针间接引用运算符;或者必须返回一个指针,被用于选择指针间接引用运算符箭头所指的内容
class Obj{ static int i,j; public: void f() const {cout<<i++<<endl;} void g() const {cout<<j++<<endl;} };int Obj::i = 47;int Obj::j = 11;class ObjContainer{ vector<obj* >a; public: void add(Obj* obj) {a.push_back(obj);} friend class SmartPointer; };class SmartPointer{ ObjContainer& oc; int index; public: SmartPointer(ObjContainer & obj):oc(obj){ index = 0; } bool operator++(){ if(index >= oc.a.size()) return false; if(oc.a[++index] == 0) return false; return true; } bool operator++(int){ return operator++(); } Obj* operator->() const{ return oc.a[index]; } };指针间接引用运算符自动的为用SmartPointer::operator->返回的Obj*调用成员函数
class Obj{ static int i,j; public: void f() const {cout<<i++<<endl;} void g() const {cout<<j++<<endl;} };int Obj::i = 47;int Obj::j = 11;class ObjContainer{ vector<obj* >a; public: void add(Obj* obj) {a.push_back(obj);} class SmartPointer; //声明友元之前必须告知该类存在 friend SmartPointer; class SmartPointer{ ObjContainer& oc; int index; public: SmartPointer(ObjContainer & obj):oc(obj){ index = 0; } bool operator++(){ if(index >= oc.a.size()) return false; if(oc.a[++index] == 0) return false; return true; } bool operator++(int){ return operator++(); } Obj* operator->() const{ return oc.a[index]; } }; SmartPointer begin(){ return SmartPointer(*this); } };operator->*
//p294
运算符成员函数基本方针
所有一元运算符建议为成员
=()[]->->*必须为成员
+= -= /= *= ^= &= |= %= >>= <<=建议为成员
所有其他二元运算符为非成员
copy-on-write
//p301引用计数
自动类型转换
构造函数转换:构造函数能把另外一个类型对象(或者引用)作为它的单个参数,该构造函数允许编译器执行自动类型转换
运算符转换:运算符重载,创建一个成员函数,该函数通过关键字operator后跟随想要转换的类型的方法,将当前类型转换为希望的类型,自动类型转换只发生在函数调用值中,而不在成员选择期间
class Three{ int i; public: Three(int ii = 0,int = 0):i(ii){} }; class Four{ int x; public: Four(int xx):x(xx){} operator Three() const {return Three(x);} };void g(Three){}int main(){ Four four(1); g(four); g(1); }
二义性错误
class Orange; class Apple{ public: operator Orange() const; }; class Orange{ public: Orange(Apple); };void f(Orange){}int main(){ Apple a; // f(a); error:二义性错误}
扇出错误:提供不止一种类型的自动转换
class Orange{};class Pear{};class Apple{ public: operator Orange() const; operator Pear() const; };void eat(Orange);void eat(Apple);int main(){ Apple a; //eat(a); error:扇出错误}相关文章:
相关视频:
Das obige ist der detaillierte Inhalt vonDenken in C++, Band 1, Lesenotizen, Schlüsselzusammenfassung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!