《C++大学教程》一本很详细的c++书籍,详细到基本可以当笔记来用 当然作为一个小白初学者还是整理一下笔记,顺便回顾。其中有一部分转载网上相关比较详细的内容
1 从C迁移到C++
c是c++的扩展以及增强;
不要假定没有数据类型就是int型,要显式的写出来(如:int main() );
初始化优于赋值(PITA规则);
在for循环中,计数变量可以在循环内声明(for(int i=0;i<5;++i));
在C++中,全局变量可以用表达式或者函数的调用来初始化,而不仅仅是使用常数;
不需要在编译时就知道数组的初始化值:(下面例子,变量a及b在堆栈中,编译时他们的值是未知的)
c++中的布尔类型,你可以这样写:void someFunction(void) { int a=1,b=2; int someArray [] ={a,b}; //Error in C ,OK in C++ }
c++中不再允许Void *型指针进行隐式转换;bool isGreater(int x,y) { return x>y; }
const说明某个对象是常量,并且所有常量必须初始化;
类型强制转换:
static_cast关键字用来执行在不同的表示中要求”等价“的转换 (static_cast<new_type>(expression))
const_cast显式的添加或取消对象的常量属性
2.命名空间
关键字 namespace
域解析运算符 ::
//using声明 namespace A { int x=1; ... } void someFunction() { using A::x; int a=x; using A:: ... ... } //using 指令 namespace A { int x=1; ... } void someFunction() { using namespace A; x=0; //use A::x ... }
using指令使用后,可以一劳永逸,对整个命名空间的所有成员都有效,非常方便。而using声明,则必须对命名空间的不同成员名称,一个一个地去声明,非常麻烦。
一般情况下,对偶尔使用的命名空间成员,应该使用命名空间的作用域解析运算符来直接给名称定位。而对一个大命名空间中的经常要使用的少数几个成员,提倡使用using声明,而不应该使用using编译指令。只有需要反复使用同一个命名空间的许多数成员时,使用using编译指令,才被认为是可取的。
3.输入输出基础
#include<iostream> using std::cout; int main() { cout<<4<<"ok"<<endl; int i; double d; std::cin>>i>>d; }
4.引用变量
引用变量即创建变量和对象的别名
#include<iostream> void addOne(int &arg) { ++arg; } int main() { int someVariable=0; addOne(someVariable); std::cout<<someVariable<<endl; } /* output: 1 */
创建引用变量:
所有引用都必须初始化,当需要支持常量对象或临时对象时,一定要使用const限定引用,但是不要用const限定引用名称本身。int data =0; int &refData=data
5.动态内存分配
关键字new后面跟随一个类型,从而在空闲存储区中分配这种类型的单个对象的空间,关键字delete来释放由new分配的空间。
int *ptrInt1=new int; //value is unknown int *ptrInt2=new int(); //value is zero double *ptrDouble=new double(3.1415); //初始化基本类型 delete ptrInt1; //释放
int *ptrInt=new int[5]; //为对象的数组创建空闲空间,在空闲空间中基本类型的数组不能被初始化
delete [] ptrInt; //释放,一定用空[].
6.类
编写类定义:
C++的关键数据隐藏原则确保类的用户不能直接操作数据成员class Circle { //all data and member functions inside the Circle };
访问限定符:private:私有类成员只能被类的成员函数访问
public:共有类成员允许不受限制的被访问,作为类的公共接口
protected:对类的成员函数以及这个类的派生类的成员函数访问
模块化举例:class Circle { public: //all public members private: //all private members };
// File circle.h #ifndef CIRCLE_H_INCLUDED #define CIRCLE_H_INCLUDED class Circle { public: void storeRadius(int newRadius); int getRadius() const; double getArea() const; private: int radius; }; #endif // CIRCLE_H_INCLUDED // File circle.cpp #include "circle.h" void Circle::storeRadius(int newRadius) { if(newRadius>0) radius=newRadius; } int Circle::getRadius() const { return radius; } double Circle::getArea() const { double const pi=3.1415; return pi*radius*radius; }
// File main.cpp #include <iostream> #include "circle.h" using namespace std; int main() { Circle real; real.storeRadius(6); cout<<"This radius is "<<real.getRadius()<<endl<<"The area "<<real.getArea()<<endl; Circle *ptr=new Circle; ptr->storeRadius(3); cout<<"this radius is "<<ptr->getRadius()<<endl<<"the area "<<ptr->getArea()<<endl; }
内联函数:
内联函数是其代码代替了通常生成的汇编语言“调用”的函数
隐式内联函数是完全在类内部定义的函数
显式内联函数出现在类定义之外,但是仍在类的头文件中。关键字inline必须在函数声明、定义、或者声明以及定义中出现
当常量成员函数需要修改非静态数据成员时,使用关键字mutable
枚举(enum):私有枚举,公有枚举
7.构造函数和析构函数
转载自:https://www.cnblogs.com/mr-wid/archive/2013/02/19/2917911.html
构造函数的显式定义
构造函数主要用来在创建对象时完成对对象属性的一些初始化等操作, 当创建对象时, 对象会自动调用它的构造函数。一般来说, 构造函数有以下三个方面的作用:
■ 给创建的对象建立一个标识符;
■ 为对象数据成员开辟内存空间;
■ 完成对象数据成员的初始化。
当用户没有显式的去定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为 "默认构造函数", 默认构造函数不能完成对象数据成员的初始化, 只能给对象创建一标识符, 并为对象中的数据成员开辟一定的内存空间。
无论是用户自定义的构造函数还是默认构造函数都主要有以下特点:
①. 在对象被创建时自动执行;
②. 构造函数的函数名与类名相同;
③. 没有返回值类型、也没有返回值;
④. 构造函数不能被显式调用。
由于在大多数情况下我们希望在对象创建时就完成一些对成员属性的初始化等工作, 而默认构造函数无法满足我们的要求, 所以我们需要显式定义一个构造函数来覆盖掉默认构造函数以便来完成必要的初始化工作, 当用户自定义构造函数后编译器就不会再为对象生成默认构造函数。
在构造函数的特点中我们看到, 构造函数的名称必须与类名相同, 并且没有返回值类型和返回值, 看一个构造函数的定义:
1 #include <iostream> 2 3 using namespace std; 4 5 class Point 6 { 7 public: 8 Point() //声明并定义构造函数 9 { 10 cout<<"自定义的构造函数被调用...\n"; 11 xPos = 100; //利用构造函数对数据成员 xPos, yPos进行初始化 12 yPos = 100; 13 } 14 void printPoint() 15 { 16 cout<<"xPos = " << xPos <<endl; 17 cout<<"yPos = " << yPos <<endl; 18 } 19 20 private: 21 int xPos; 22 int yPos; 23 }; 24 25 int main() 26 { 27 Point M; //创建对象M 28 M.printPoint(); 29 30 return 0; 31 }
编译运行的结果:自定义的构造函数被调用... xPos = 100 yPos = 100 Process returned 0 (0x0) execution time : 0.453 s Press any key to continue.
代码说明:
在Point类的 public 成员中我们定义了一个构造函数Point() , 可以看到这个Point构造函数并不像 printPoint 函数有个void类型的返回值, 这正是构造函数的一特点。在构造函数中, 我们输出了一句提示信息, "自定义的构造函数被调用...", 并且将对象中的数据成员xPos和yPos初始化为100。
在 main 函数中, 使用 Point 类创建了一个对象 M, 并调用M对象的方法 printPoint 输出M的属性信息, 根据输出结果看到, 自定义的构造函数被调用了, 所以 xPos和yPos 的值此时都是100, 而不是一个随机值。
需要提示一下的是, 构造函数的定义也可放在类外进行。
三、有参数的构造函数
在上个示例中实在构造函数的函数体内直接对数据成员进行赋值以达到初始化的目的, 但是有时候在创建时每个对象的属性有可能是不同的, 这种直接赋值的方式显然不合适。不过构造函数是支持向函数中传入参数的, 所以可以使用带参数的构造函数来解决该问题。
1 #include <iostream> 2 3 using namespace std; 4 5 class Point 6 { 7 public: 8 Point(int x = 0, int y = 0) //带有默认参数的构造函数 9 { 10 cout<<"自定义的构造函数被调用...\n"; 11 xPos = x; //利用传入的参数值对成员属性进行初始化 12 yPos = y; 13 } 14 void printPoint() 15 { 16 cout<<"xPos = " << xPos <<endl; 17 cout<<"yPos = " << yPos <<endl; 18 } 19 20 private: 21 int xPos; 22 int yPos; 23 }; 24 25 int main() 26 { 27 Point M(10, 20); //创建对象M并初始化xPos,yPos为10和20 28 M.printPoint(); 29 30 Point N(200); //创建对象N并初始化xPos为200, yPos使用参数y的默认值0 31 N.printPoint(); 32 33 Point P; //创建对象P使用构造函数的默认参数 34 P.printPoint(); 35 36 return 0; 37 }
编译运行的结果:自定义的构造函数被调用... xPos = 10 yPos = 20 自定义的构造函数被调用... xPos = 200 yPos = 0 自定义的构造函数被调用... xPos = 0 yPos = 0 Process returned 0 (0x0) execution time : 0.297 s Press any key to continue.
代码说明:
在这个示例中的构造函数Point(int x = 0, int y = 0) 使用了参数列表并且对参数进行了默认参数设置为0。在 main 函数中共创建了三个对象 M, N, P。
M对象不使用默认参数将M的坐标属性初始化10和20;
N对象使用一个默认参数y, xPos属性初始化为200;
P对象完全使用默认参数将xPos和yPos初始化为0。
三、构造函数的重载
构造函数也毕竟是函数, 与普通函数相同, 构造函数也支持重载, 需要注意的是, 在进行构造函数的重载时要注意重载和参数默认的关系要处理好, 避免产生代码的二义性导致编译出错, 例如以下具有二义性的重载:
Point(int x = 0, int y = 0) //默认参数的构造函数 { xPos = x; yPos = y; } Point() //重载一个无参构造函数 { xPos = 0; yPos = 0; }
在上面的重载中, 当尝试用 Point 类重载一个无参数传入的对象 M 时, Point M; 这时编译器就报一条error: call of overloaded 'Point()' is ambiguous 的错误信息来告诉我们说 Point 函数具有二义性, 这是因为Point(int x = 0, int y = 0) 全部使用了默认参数, 即使我们不传入参数也不会出现错误, 但是在重载时又重载了一个不需要传入参数了构造函数Point(), 这样就造成了当创建对象都不传入参数时编译器就不知道到底该使用哪个构造函数了, 就造成了二义性。
四、初始化表达式
对象中的一些数据成员除了在构造函数体中进行初始化外还可以通过调用初始化表来进行完成, 要使用初始化表来对数据成员进行初始化时使用: 号进行调出, 示例如下:
Point(int x = 0, int y = 0):xPos(x), yPos(y) //使用初始化表 { cout<<"调用初始化表对数据成员进行初始化!\n"; }
在 Point 构造函数头的后面, 通过单个冒号: 引出的就是初始化表, 初始化的内容为 Point 类中int型的 xPos 成员和 yPos成员, 其效果和 xPos = x; yPos = y; 是相同的。
与在构造函数体内进行初始化不同的是, 使用初始化表进行初始化是在构造函数被调用以前就完成的。每个成员在初始化表中只能出现一次, 并且初始化的顺序不是取决于数据成员在初始化表中出现的顺序, 而是取决于在类中声明的顺序。
此外, 一些通过构造函数无法进行初始化的数据类型可以使用初始化表进行初始化, 如: 常量成员和引用成员, 这部分内容将在后面进行详细说明。使用初始化表对对象成员进行初始化的完整示例:
View Code
五、析构函数
与构造函数相反, 析构函数是在对象被撤销时被自动调用, 用于对成员撤销时的一些清理工作, 例如在前面提到的手动释放使用 new 或 malloc 进行申请的内存空间。析构函数具有以下特点:
■ 析构函数函数名与类名相同, 紧贴在名称前面用波浪号 ~ 与构造函数进行区分, 例如:~Point();
■ 构造函数没有返回类型, 也不能指定参数, 因此析构函数只能有一个, 不能被重载;
■ 当对象被撤销时析构函数被自动调用, 与构造函数不同的是, 析构函数可以被显式的调用, 以释放对象中动态申请的内存。
当用户没有显式定义析构函数时, 编译器同样会为对象生成一个默认的析构函数, 但默认生成的析构函数只能释放类的普通数据成员所占用的空间, 无法释放通过 new 或 malloc 进行申请的空间, 因此有时我们需要自己显式的定义析构函数对这些申请的空间进行释放, 避免造成内存泄露。
1 #include <iostream> 2 #include <cstring> 3 4 using namespace std; 5 6 class Book 7 { 8 public: 9 Book( const char *name ) //构造函数 10 { 11 bookName = new char[strlen(name)+1]; 12 strcpy(bookName, name); 13 } 14 ~Book() //析构函数 15 { 16 cout<<"析构函数被调用...\n"; 17 delete []bookName; //释放通过new申请的空间 18 } 19 void showName() { cout<<"Book name: "<< bookName <<endl; } 20 21 private: 22 char *bookName; 23 }; 24 25 int main() 26 {