当对一个对象调用成员函数时,编译程序先将对象的地址赋给 this 指针,然后调用成员函数,每次成员函数存取数据成员时,都隐式使用 this 指针。
当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向这个成员函数所在的对象的指针。
this 指针被隐含地声明为: ClassName *const this,这意味着不能给 this 指针赋值;在 ClassName 类的 const 成员函数中,this 指针的类型为:const ClassName* const,这说明不能对 this 指针所指向的这种对象是不可修改的(即不能对这种对象的数据成员进行赋值操作);
this 并不是一个常规变量,而是个右值,所以不能取得 this 的地址(不能 &this)。
在以下场景中,经常需要显式引用this 指针:
为实现对象的链式引用;
为避免对同一对象进行赋值操作;
在实现一些数据结构时,如 list。
inline 内联函数
特征
相当于把内联函数里面的内容写在调用内联函数处;
相当于不用执行进入函数的步骤,直接执行函数体;
相当于宏,却比宏多了类型检查,真正具有函数特性;
编译器一般不内联包含循环、递归、switch 等复杂操作的内联函数;
在类声明中定义的函数,除了虚函数的其他函数都会自动隐式地当成内联函数。
使用
inline 使用
// 声明1(加 inline,建议使用)inline int functionName(int first, int second,...);// 声明2(不加 inline)int functionName(int first, int second,...);// 定义inline int functionName(int first, int second,...) {/****/};// 类内定义,隐式内联class A { int doA() { return 0; } // 隐式内联}// 类外定义,需要显式内联class A { int doA();}inline int A::doA() { return 0; } // 需要显式内联
struct 作为数据结构的实现体,它默认的数据访问控制是 public 的,而 class 作为对象的实现体,它默认的成员变量访问控制是 private 的。
union 联合
联合(union)是一种节省空间的特殊的类,一个 union 可以有多个数据成员,但是在任意时刻只有一个数据成员可以有值。当某个成员被赋值后其他成员变为未定义状态。联合有如下特点:
默认访问控制符为 public
可以含有构造函数、析构函数
不能含有引用类型的成员
不能继承自其他类,不能作为基类
不能含有虚函数
匿名 union 在定义所在作用域可直接访问 union 成员
匿名 union 不能包含 protected 成员或 private 成员
全局匿名联合必须是静态(static)的
union 使用
#include<iostream>union UnionTest { UnionTest() : i(10) {}; int i; double d;};static union { int i; double d;};int main() { UnionTest u; union { int i; double d; }; std::cout << u.i << std::endl; // 输出 UnionTest 联合的 10 ::i = 20; std::cout << ::i << std::endl; // 输出全局静态匿名联合的 20 i = 30; std::cout << i << std::endl; // 输出局部匿名联合的 30 return 0;}
C 实现 C++ 类
C 实现 C++ 的面向对象特性(封装、继承、多态)
封装:使用函数指针把属性与方法封装到结构体中
继承:结构体嵌套
多态:父类与子类方法的函数指针不同
[Can you write object-oriented code in C? closed]
explicit(显式)关键字
explicit 修饰构造函数时,可以防止隐式转换和复制初始化
explicit 修饰转换函数时,可以防止隐式转换,但 按语境转换 除外
explicit 使用
struct A{ A(int) { } operator bool() const { return true; }};struct B{ explicit B(int) {} explicit operator bool() const { return true; }};void doA(A a) {}void doB(B b) {}int main(){ A a1(1); // OK:直接初始化 A a2 = 1; // OK:复制初始化 A a3{ 1 }; // OK:直接列表初始化 A a4 = { 1 }; // OK:复制列表初始化 A a5 = (A)1; // OK:允许 static_cast 的显式转换 doA(1); // OK:允许从 int 到 A 的隐式转换 if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换 bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换 bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换 bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化 B b1(1); // OK:直接初始化 B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化 B b3{ 1 }; // OK:直接列表初始化 B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化 B b5 = (B)1; // OK:允许 static_cast 的显式转换 doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换 if (b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换 bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换 bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换 bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化 return 0;}
friend 友元类和友元函数
能访问私有成员
破坏封装性
友元关系不可传递
友元关系的单向性
友元声明的形式及数量不受限制
using
using 声明
一条 using 声明 语句一次只引入命名空间的一个成员。它使得我们可以清楚知道程序中所引用的到底是哪个名字。如:
using namespace_name::name;
构造函数的 using 声明
在 C++11 中,派生类能够重用其直接基类定义的构造函数。
class Derived : Base {public: using Base::Base; /* ... */};
如上 using 声明,对于基类的每个构造函数,编译器都生成一个与之对应(形参列表完全相同)的派生类构造函数。生成如下类型构造函数:
Derived(parms) : Base(args) { }
using 指示
using 指示 使得某个特定命名空间中所有名字都可见,这样我们就无需再为它们添加任何前缀限定符了。如:
using namespace_name name;
尽量少使用 using 指示 污染命名空间
一般说来,使用 using 命令比使用 using 编译命令更安全,这是由于它只导入了指定的名称。如果该名称与局部名称发生冲突,编译器将发出指示。using编译命令导入所有的名称,包括可能并不需要的名称。如果与局部名称发生冲突,则局部名称将覆盖名称空间版本,而编译器并不会发出警告。另外,名称空间的开放性意味着名称空间的名称可能分散在多个地方,这使得难以准确知道添加了哪些名称。
using 使用
尽量少使用 using 指示
using namespace std;
应该多使用 using 声明
int x;std::cin >> x ;std::cout << x << std::endl;
或者
using std::cin;using std::cout;using std::endl;int x;cin >> x;cout << x << endl;
int main(){ T* t = new T(); // 先内存分配 ,再构造函数 delete t; // 先析构函数,再内存释放 return 0;}
定位 new
定位 new(placement new)允许我们向 new 传递额外的地址参数,从而在预先指定的内存区域创建对象。
new (place_address) typenew (place_address) type (initializers)new (place_address) type [size]new (place_address) type [size] { braced initializer list }
place_address 是个指针
initializers 提供一个(可能为空的)以逗号分隔的初始值列表
delete this 合法吗?
Is it legal (and moral) for a member function to say delete this?
合法,但:
必须保证 this 对象是通过 new(不是 new[]、不是 placement new、不是栈上、不是全局、不是其他对象成员)分配的
Class unique_ptr 实现独占式拥有(exclusive ownership)或严格拥有(strict ownership)概念,保证同一时间内只有一个智能指针可以指向该对象。你可以移交拥有权。它对于避免内存泄漏(resource leak)——如 new 后忘记 delete ——特别有用。
shared_ptr
多个智能指针可以共享同一个对象,对象的最末一个拥有着有责任销毁对象,并清理与该对象相关的所有资源。
支持定制型删除器(custom deleter),可防范 Cross-DLL 问题(对象在动态链接库(DLL)中被 new 创建,却在另一个 DLL 内被 delete 销毁)、自动解除互斥锁
将文件间的编译依存关系降至最低(如果使用 object references 或 object pointers 可以完成任务,就不要使用 objects;如果能够,尽量以 class 声明式替换 class 定义式;为声明式和定义式提供不同的头文件)
确定你的 public 继承塑模出 is-a(是一种)关系(适用于 base classes 身上的每一件事情一定适用于 derived classes 身上,因为每一个 derived class 对象也都是一个 base class 对象)
避免遮掩继承而来的名字(可使用 using 声明式或转交函数(forwarding functions)来让被遮掩的名字再见天日)
区分接口继承和实现继承(在 public 继承之下,derived classes 总是继承 base class 的接口;pure virtual 函数只具体指定接口继承;非纯 impure virtual 函数具体指定接口继承及缺省实现继承;non-virtual 函数具体指定接口继承以及强制性实现继承)
明智而审慎地使用 private 继承(private 继承意味着 is-implemented-in-terms-of(根据某物实现出),尽可能使用复合,当 derived class 需要访问 protected base class 的成员,或需要重新定义继承而来的时候 virtual 函数,或需要 empty base 最优化时,才使用 private 继承)
了解 typename 的双重意义(声明 template 类型参数是,前缀关键字 class 和 typename 的意义完全相同;请使用关键字 typename 标识嵌套从属类型名称,但不得在基类列(base class lists)或成员初值列(member initialization list)内以它作为 base class 修饰符)
学习处理模板化基类内的名称(可在 derived class templates 内通过 this-> 指涉 base class templates 内的成员名称,或藉由一个明白写出的 “base class 资格修饰符” 完成)
对于想要入门C++的同学来说,《C++ Primer》是一本不能错过的入门书籍,它用平易近人的实例化教学激发学生的学习兴趣,帮助学生一步步走进C++的大门。在本文中,作者Jacen用两万多字总结了《C++ Primer 中文版(第五版)》1-16章的阅读要点,可以作为该书的阅读参考。注:原书更为详细,本文仅作学习交流使用。
*iter // 解引用,返回引用 iter->mem // 等价于 (*iter).mem ++iter --iter iter1 == iter2 iter1 != iter2 iter + n iter - n iter += n iter -= n iter1 - iter2 // 两个迭代器相减的结果是它们之间的距离 >, >=, <, <= // 位置比较
Sales_data add(const Sales_data &lhs, const Sales_data &rhs) { Sales_data sum = lhs; // copy data members from lhs into sum sum.combine(rhs); // add data members from rhs into sum return sum; }
// transactions contain ISBN, number of copies sold, and sales price istream& read(istream &is, Sales_data &item) { double price = 0; is >> item.bookNo >> item.units_sold >> price; item.revenue = price * item.units_sold; return is; }
// will hold a line and word from input, respectively string line, word;
// will hold all the records from the input vector<PersonInfo> people;
// read the input a line at a time until end-of-file (or other error) while (getline(is, line)) { PersonInfo info; // object to hold this record's data istringstream record(line); // bind record to the line we just read record >> info.name; // read the name while (record >> word) // read the phone numbers info.phones.push_back(word); // and store them people.push_back(info); // append this record to people }
// for each entry in people for (vector<PersonInfo>::const_iterator entry = people.begin(); entry != people.end(); ++entry) { ostringstream formatted, badNums; // objects created on each loop
// for each number for (vector<string>::const_iterator nums = entry->phones.begin(); nums != entry->phones.end(); ++nums) { if (!valid(*nums)) { badNums << " " << *nums; // string in badNums } else // ``writes'' to formatted's string formatted << " " << format(*nums); } if (badNums.str().empty()) // there were no bad numbers os << entry->name << " " // print the name << formatted.str() << endl; // and reformatted numbers else // otherwise, print the name and bad numbers cerr << "input error: " << entry->name << " invalid number(s) " << badNums.str() << endl; }
C c; // 默认构造函数 C c1(c2) C c1=c2 C c{a,b,c...} // 列表初始化 C c={a,b,c...} C c(b,e) // c初始化为迭代器b和e指定范围中的元素的拷贝 // 只有顺序容器(不包括array)的构造函数才能接受大小参数 C seq(n) C seq(n,t)
new : 从自由空间分配内存。new T 分配并构造一个类型为T的指针。如果T是一个数组类型,new 返回一个指向数组首元素的指针。类似的,new [n] T 分配 n 个类型为T的对象,并返回指向数组首元素的指针。空悬指针:一个指针,指向曾经保存一个对象但现在已释放的内存。智能指针:标准库类型。负责在恰当的时候释放内存。 第十三章 拷贝控制 P440-P486五种拷贝控制操作:拷贝构造函数、拷贝赋值运算符、移动构造函数、移动赋值运算符、析构函数。拷贝构造函数、移动构造函数定义了当用同类型的另一个对象初始化本对象时做什么。拷贝赋值运算符、移动赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么。析构函数定义了当此类型对象销毁时做什么。
13.1 拷贝、赋值与销毁
(1)拷贝构造函数拷贝构造函数的第一个参数必须是一个引用类型。
class Foo { public : Foo(); // 默认构造函数 Foo(const Foo&); // 拷贝构造函数 }
Kernighan和Ritchie的《The C Programming Language》(中译名《C程序设计语言》)堪称经典中的经典,不过旧版的很多内容都已过时,和现在的标准C语言相去甚远,大家一定要看最新的版本,否则不如不看。另外,即使是最经典最权威的书,也没有办法面面俱到,所以手边常备一本《C语言参考手册》是十分必要的。
《C语言参考手册》就是《C Reference Manual》,是C语言标准的详细描述,包括绝大多数C标准库函数的细节,算得上是最好的标准C语言的工具书。顺便提一句,最新的《C程序设计语言》是根据C89标准修订的,而《C语言参考手册》描述的是C99标准,二者可能会有些出入,建议按照C99标准学习。还有一本《C和指针》,写得也是相当地不错,英文名是《Pointers on C》,特别地强调指针的重要性,算是本书的一个特点吧。
如果你已经啃完了一本C语言教材,想要更进一步,那么有两本书你一定要看。首先是《C Traps and Pitfalls》(中译名《C陷井与缺陷》),很薄的一本小册子,内容非常非常地有趣。要注意一点,这本书是二十多年前写成的,里面提到的很多C语言的缺陷都已被改进,不过能够了解一些历史也不是什么坏事。然后你可以挑战一下《Expert C Programming》(中译名《C专家编程》),书如其名,这本书颇具难度,一旦你仔细读完并能透彻理解,你便可以放心大胆地在简历上写“精通C语言”了。
所以我建议初学者应该以Visual C++ 6.0(不是VisualC++ .NET)或者Dev C++作为主要的学习环境,而且千万不要在IDE的使用技巧上过多纠缠,因为今后你一定要转向Unix环境的。Visual C++ 6.0使用很方便,调试也很直观,但其默认的编译器对C标准的支持并不好,而Dev C++使用gcc编译器,对C99的标准都支持良好。
使用顺带提一下,很多大学的C语言课程还在使用Turbo C 2.0作为实验环境,这是相当不可取的,原因其一是TC 2.0对C标准几乎没有支持,其二是TC 2.0编译得到的程序是16位的,这对今后理解32位的程序会造成极大的困扰(当然,用djgpp之类的东西可以使TC 2.0编译出32位程序,不过那过于复杂了)。