C++跟C第一个不同, 打印语句。
cout << 打印内容 <<endl
---------------------------------------------------------
cout是一个输出流对象, 它是console out (控制台输出)的缩写。
cout 属于 basic_ostream 类的对象。ostream类在iostream头文件中定义
cin 属于 istream
---------------------------------------------------------
using namespace std 名字空间
之前用*.h 到了 C99, 之后将.h 去掉, 用 using namespace std
是C++标准库所使用的所有表示符(即类、函数、对象等的名称) 都是在同一个特殊的名字空间std(中)来定义的。
如果没有使用这条指令, 我们将需要使用std::out 这样的语法来调用输出流对象.
所以通俗理解, 这条指令是让我们偷懒用的。
---------------------------------------------------------
endl代表结束。
<< 这个操作符不是C语言里边位运算的左移操作符吗? 难道C++里边改规则了?
事实上不是这样的, 它只不过体现了C++的另一个特点: 支持重载。
---------------------------------------------------------
ungetc(ch, stdin);// 将变量ch中存放的字符退回给stdin输入流
---------------------------------------------------------
while(cin >> i){
sum += i;
while(cin.peek() == ''){
cin.get();
}
if(cin.peek() == '\n'){
break;
}
}
cout << sum << endl
while(cin >> i)中 当到达文件尾或者提取操作符遇到一个非法值,这个返回将是FALSE。
非法: 比如输入小数给i时,i是整形, 则会非法。 比如3.14 则i = 3 。
---------------------------------------------------------
C和C++事实上在声明变量上有点不同, C++允许我们在程序的任意位置声明变量。
这大大的提高了大型C++程序的可读性。 因为这样子我们就可以在实际需要使用变量的时候来声明他们。
---------------------------------------------------------
cin.ignore(7) //忽略前7个字符
cin.getline(buf, 10)//获取一行的前10个字符
cin.peek();
cin.get();
cin.read(buf, 20);
cin.gcount(); cout << 字符串收集到的字符数为:<< cin.gcount();<< endl
---------------------------------------------------------
const 比宏定义好很多。
---------------------------------------------------------
cout.precision(i); 输出小数的值,按精度。
cout.width();
---------------------------------------------------------
ifstream 输入文件流类
ofstream 输出文件流类
都属于 fstream
ifstream in(test.txt,);
//in.open(test.txt);
//ifstream in(test.txt,mode);
//ifstream in(test.txt,mode|mode);
ofstream out(test.txt);
//out.open(test.txt);
//ofstream out(test.txt, mode);
//ofstream out(test.txt, mode|mode);
mode: in(可读)、out(可写)、binary(打开二进制)、app(追加文件末尾)、trunk(覆盖)、nocreate(打开的文件并不存在,然后调用open函数,将无法创建)、noreplace(打开的文件已存在,试图调用open函数打开时将返回一个错误)
fstream fp("test.txt", in|out);
fp.seekg(ios:beg);//使得文件指针,指向文件头
fp.seekg(ios:end);//使得文件指针,指向文件尾
---------------------------------------------------------
cin对象下几个简单的方法:
-eof() 文件末尾
-fail() cin无法工作,返回true
-bad() 如果cin因为比较严重的原因(内存不足)而无法工作,返回true。
-good() 如果以上情况都没有发生,返回true。
---------------------------------------------------------
函数重载:
对函数重载, 可以简化编程工作,提高代码可读性。
注意: 重载不是一个面向对象的特征。 只是简化编程工作。
我们只能通过不同的参数来区分重载, 返回值不可以。
重载的目的:
对函数进行重载,目的是为了方便堆不同数据类型进行同样的处理。
---------------------------------------------------------
std:string 类型其实是在C++标准库定义的一个对象,其内建功能非常之多。
---------------------------------------------------------
int* p1,p2,p3;
注意: 上面是申明一个指针,其他两个为整形变量。
int *p1, *p2, *p3;
void *p;
注意: 对一个无类型指针(void* )进行解引用前,必须先把它转换为一种适当的数据类型。
---------------------------------------------------------
reinterpret_cast<type-id> (expression);
*p + 1;
*(p+1);
---------------------------------------------------------
泛型程序设计:
template <typename elemType>
void print (elemType *pBegin, elemType *pEnd)
{
}
---------------------------------------------------------
引用传递:
swap(int& x, int& y)
{
}
---------------------------------------------------------
enum weekdays{Monday,Tuesday, Wednesday, Thursday, Friday};
weekdays today;
today = Thursday;
注意: 我们这里不需要使用引号, 因为枚举值不是字符串。
编译器会按照各个枚举值在定义时出现的先后顺序把它们与
0 到 n-1 的整数(n是枚举值的总个数)分别关联起来。
使用枚举类型好处有两个:
1、它们对变量的可取值加以限制
2、它们可以用做switch条件语句的case标号。
(因为字符串是不能用作标号使用的)
---------------------------------------------------------
class MyFirstClass{
};
注意:
1: 类名的第一个字母要大写;
2: 类声明的末尾要加上分号;
通常:
类里面的变量我们称之为属性;
函数称之为方法;
---------------------------------------------------------
方法的定义通常安排在类声明的后面:
例子:
class Car{
public:
std::String color;
std::String engine;
float gas_tank;
unsigned int wheel;
void fill_tank(float liter);
void runing();
}
void Car:: fill_tank(float liter)
{
gas_tank += liter;
}
---------------------------------------------------------
可以将类的声明和类中函数的定义部分分别存入两个文件,前者存入.h的头文件中,后者存入相应的.cpp文件。
C++允许在类中声明常量,但不允许对它进行赋值。
class Car{
public:
const float TANKSIZE = 85; //会报错,不允许这样操作。
};
绕开这一限制的方法就是创建一个静态常量
class Car{
public:
static const float TANKSIZE = 85;
};
---------------------------------------------------------
class Car{
...
}car1,car2;
这种做法在C++里是允许的,但是不建议这样使用。
---------------------------------------------------------
Car car1, car2;
car1.setColor("WHITE");
car2 = car1;
把一个对象赋值给另一个同类对象,将会自动使同名的属性有同样的值。
---------------------------------------------------------
构造器:
1、构造器的名字必须和它所在类的名字一样, 大小写保持一致;
2、系统在创建某个类的实例时,会第一时间自动调用这个类的构造器;
3、构造器永远不会返回任何值。
注意:
每个类至少有一个构造函数,如果你没有在类中定义一个构造器,编译器就会默认使用如下语法替你定义一个:
ClassName:: ClassName(){};
这是一个没有任何代码内容的构造器。
所以为了避免这个默认的构造器, 我们通常要自己手动创建咱们自己的构造器。
--------------
插播://数组可以是任意类型, 所以数组也可以是对象的数组。
Car array[10];
array[x].running; //x为数组下标
--------------
---------------------------------------------------------
析构器:在销毁一个对象时,系统会调用析构器。
一般来说,构造器用来完成时限的初始化和准备工作(申请内存),
析构器用来完成时候的所必须的清理工作(清理内存)。
析构器在构造器的基础上在前面多了一个波浪符号"~"
1、析构器永远不会返回值
2、析构器不带任何参数: 比如: ~ClassName();
3、析构器有时可有可无,但是在一些复制的类中,往往析构器至关重要,不然容易引起内存泄漏。
---------------------------------------------------------
this
使用this指针的基本原则是: 如果代码不存在二义性隐患,就不必使用this指针。
this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象的地址。
每当我们调用一个方法的时候,this指针都会随着你提供的输入参数,被秘密的传递给那个方法。
正因为如此,我们才能在方法里像使用一个局部变量那样使用this指针。
---------------------------------------------------------
继承:
简单的说,通过继承机制,程序员可以对现有的代码进行进一步的扩展,并应用在新的程序中。
基类: 也叫父类、超类。
子类:
语法:
class SubClass : public SuperClass{...};
例子:
class Pig : public Animal{...};
public: 后续在说。
---------------------------------------------------------
访问控制:
所谓的访问控制, 就是C++提供了一种用来保护类的方法和属性的手段。
public:任何代码都可以访问
protected:这个类本身和它的子类
private:只是这个类本身
使用方法:
在类中的某个地方写出一个访问级别并在其后加上一个冒号,从那个地方开始往后的所有方法和属性都将受到相应的保护,
直到遇到下一个访问级别或者到达这个类的末尾为止。
private的好处: 今后可以只修改某个类的内部实现,而不必重新修改整个程序。避免了“牵一发而动全身”
在同一个类中可以使用多个public、protected、private,但是最好把你的元素集中到一个地方,
这样的代码可读性会好很多。
在编写你的类定义代码时, 应该从public开始写,然后是protected,最后是private。
虽然编译器并不挑剔,但是项目大时,好的顺序可以为你节省大量的时间。
---------------------------------------------------------
bug无法回避的原因:
我们无法模拟各种情况的输入和修改带来的影响。
---------------------------------------------------------
class Pig: public Animal {};
这里的public是在告诉编译器,继承的方法和属性的访问级别不发生任何改变。即public仍可以被所有代码来访问,protected只能由基类的子类进行访问,private则只能由基类本身访问。
---------------------------------------------------------
覆盖:
在类中重新声明这个方法,然后在改写一下它的实现代码(就像它是一个增加的方法一样)。
1、不同的范围,比如基类和子类之间
2、函数名字相同
3、参数相同
4、基类函数必须有virtual关键字
重载:
可以定义多个同名的方法(函数),只是它们的输入参数不同(因为编译器是依靠不同的输入参数来区分不同的方法的)。
注意: 重载并不是一个真正的面向对象特性,它只是可以简化编程工作。
1、在同一个类中
2、函数名字相同
3、参数不同
---------------------------------------------------------
友元关系:
友元关系是类之间的一个特殊关系,这种关系不仅允许友元类访问对方的public方法和属性,还允许友元访问对方的protected和private方法和属性。
声明为友元关系语法:
在类声明的某个地方加上一条 friend class 类名 就行了。
---------------------------------------------------------
面向对象编程技术的一个重要特征是用一个对象把数据和对数据处理的方法封装在一起。
---------------------------------------------------------
static:
C++允许我们把一个或多个成员声明为属于某个类,而不是仅属于该类的对象。这么做的好处是程序员可以在没有创建任何对象的情况下调用有关的方法。
另外一个好处是能够让有关的数据人在该类的所有对象间共享。
注意:
静态成员是所有对象共享的,所以不能在静态方法里访问非静态的元素。
非静态方法可以访问类的静态成员,也可以访问类的非静态成员。
静态方法:
建议这样使用:ClassName::methodName();
不建议这样使用:ObjectName.methodName();
---------------------------------------------------------
int *pointer = new int;
*pointer = 110;
std:count<<*pointer;
delete pointer;
程序中每一个new操作,必须要有一个与之对应的delete操作。
---------------------------------------------------------
声明一个虚方法:
virtual void play();
注意:虚方法是继承的, 一旦在基类中把某个方法声明为虚方法,在子类里就不可能再把它声明为一个非虚方法了。
注意:
1、如果拿不准要不要把某个方法声明为虚方法,那么就把它声明为虚方法好了。
2、在基类里把所有的方法都声明为虚方法会让最终生成的可执行代码的速度变得稍微慢一些,但好处是可以一劳永逸地确保程序的行为符合你的预期。
3、在实现一个多层次的类继承关系的时候,最顶级的基类应该只有虚方法。
4、析构器都是虚方法,从编译的角度看,它们只是普通的方法,如果它们不是虚方法,编译器就会根据它们在编译时的类型而调用那个在基类定义的版本(构造器),那样往往会导致内存泄漏。
---------------------------------------------------------
抽象方法:
把某个方法声明为一个抽象方法等于告诉编译器,这个方法必不可少,但是现在还不能为它提供一个实现。
void play() = 0;
---------------------------------------------------------
多态性:
多态性指的是用一个名字定义不同的函数,调用同一个名字的函数,却执行不同的操作,从而实现传说中的“一个接口,多种方法”
-----编译时的多态性: 通过重载实现。
-----运行时的多态性: 通过虚函数实现。
---------------------------------------------------------
当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里边存放着虚函数指针。
所以我们为了节省资源,只有当一个类被用来作为基类的时候,我们才把析构函数写成虚函数。
---------------------------------------------------------
运算符重载:
运算符重载是通过定义函数实现的,运算符重载实质上是函数的重载。
函数类型 operator 运算符名称(形参列表)
{
对运算符的重载处理
}
例子:
int operator+(int a, int b)
{
return a+b;
}
C++ 不允许用户自定义新的运算符,只能对已有的C++运算符进行重载。
注意:
以下运算符C++不可以进行重载:
.(成员访问运算符)
.*(成员指针访问运算符)
::(域运算符)
sizeof(尺寸运算符)
?: (条件运算符)
重载不能改变运算符运算对象(操作数)个数
重载不能改变运算符的优先级别
重载不能改变运算符的结合性
重载运算符的函数不能有默认的参数
重载的运算符不能有和用户定义的自定义类型的对象一起使用,其参数至少应该有一个是类的对象或类对象的引用。(也就是说,参数不能全部都是C++的标准类型,这样是为了防止用户修改用于标准类型结构的运算符属性)
类中的成员函数,都存在一个隐藏参数,this
c1 + c2
编译器会解释为: c1.operaor+(c2);
即 + 号会解释为类的成员函数来调用。
对象c1调用运算符重载函数,并以表达式中第二个参数(运算符右侧的类对象c2)作为函数的实参。
运算符重载函数除了可以作为类的成员函数外,还可以是非成员函数。放在类外,作为友元函数存在。
但是友元函数会破坏类的封装,因此从原则上说,要尽量将运算符函数作为成员函数。
---------------------------------------------------------
多继承:
class TeachingStudent: public Student, public Teacher
{
};
---------------------------------------------------------
虚继承:
TeachingStudent类继承了Teaching 和 Student 两个类, 因而继承了两组Person类的属性,这在某些时候完全有道理,例如:classes属性。但它也有可能引起麻烦,例如发生在name属性身上的情况。
为了避免上面的问题,C++发明者提供了虚继承的概念。
通过虚继承某个基类,就是在告诉编译器:从当前这个类在派生出来的子类只能拥有该基类的一个实例。
语法:
class Teacher : virtual public Person
{
};
---------------------------------------------------------
assert:
assert.h
assert()函数 需要一个参数,它将测试这个输入参数的真假状态。如果为真,则Do nothing。如果为假,则Do something。
我们可以利用它在某个程序里的关键假设不成立时立刻停止该程序的执行报错,从而避免发生更严重的错误。
---------------------------------------------------------
try{
可能出错的代码
}
catch{
出错后执行的代码
}
注意:
1、throw之后的语句不执行。
2、如果一个try语句没有找到与之对应的catch语句时,它抛出的异常将中止程序的运行。
exception异常类
如果你打算使用对象作为异常,请记住这样一个原则,以值传递方式抛出异常,以引用传递方式捕获异常。
---------------------------------------------------------
动态内存:
从内存块申请一些内存,需要使用new语句,它将根据你提供的数据类型分配一块大小适当的内存。
new语句返回新分配地址块的起始地址。
如果new语句申请时,没有足够的可用内存,那么new语句将抛出std::bad_alloc异常。
用delete语句释放内存。new是在堆中申请内存的。
另外delete之后,将指针复制为NULL。
int *p = new int;
将指针复制为NULL的含义:
指针不在指向任何东西。
注意:
1、
静态内存这个术语与C++保留字static没有任何关系。
静态内存意思是指内存块的长度在程序编译时被设定为一个固定的值
而这个值在程序运行时无法改变。
2、
new语句返回的内存块应该进行初始化,不然里面的值垃圾。
3、 new出的内存,在调用delete之后, 那个指针还是存在的,只不过是之前指针指向的内存空间被释放了。
-------------------------------
count = 10;
int *p = new int[count];
delete[] p;
---------------------------------------------------------
函数指针:
指针函数: 就是函数的返回值是指针。
---------------------------------------------------------
副本构造器:
逐位赋值:我们可以把一个对象赋值给一个类型与之相同的变量。
Object a;
Object b = a;
这种情况编译器会自动进行副本构造器, 即使这个时候我们重载了=,编译器也会进行副本构造器,
我们想要避免,我们必须要自己进行自定义一个副本构造器。
Myclass(const Myclass $obj1)
---------------------------------------------------------
强制类型转换
假设有两个类: A类 B类 A类继承B类
A *pa = new A;
B *pb = dynamic_cast<A*> (pa);
格式:
dynamic_cast<MyClass*> (pvalue)
如果pvalue的类型不是Myclass类(或者其子类)的指针,这个操作符将返回NULL。
---------------------------------------------------------
int *x;
x = new int[1000];
delete x;
x = NULL;
---------------------------------------------------------
如果被定义在任何一个函数的外部,变量将拥有全局作用域。
动态内存不存在作用域的问题,一旦被分配,内存块就可以在程序的任何地方使用。
单需要注意的是,用来保存其地址的指针变量是受作用域影响的。
---------------------------------------------------------
专注那些我们应该去做的事情,并坚持下去。
---------------------------------------------------------
命名空间和模块化编程:
namespace -- 命名空间
命名空间其实就是由用户定义的范围,同一个命名空间里的东西只要在这个命名空间有独一无二的名字就行了。
namespace myNamespace{
//全部东西
}
在头文件、源文件中分别用同一个命名空间将要保护的用大括号扩上。
注意: 在最末尾不需要加上分号。
如果某个东西是在命名空间里定义的, 程序将不能立刻使用它。
命名空间可以让你使用同一个标识符而不会导致冲突。这正是命名空间额全部意义所在:
把东西放它们自己的(命名空间)小盒子里,不让它们与可能有着相同名字的其他东西发生冲突。
想要访问在某个命名空间里定义的东西,有三种方法。
1. std::cout<< " "; 作用域
2.使用using namespace std; 但是不建议这样使用, 因为把命名空间里的东西带到全局作用域里,跟我们使用命名空间的本意相违背。
3.使用using指令把自己想要的特定命名从命名空间提取到全局作用域:
using std::cout;
cout << " ";
注意: using指令出现的位置决定着从命名空间里提取出来的东西能在哪个作用域内使用。
比如: 你把它放在所有函数声明的前面,那么它将拥有全局性, 如果你把它放在某个函数里,那么它将只是在这一个函数里可以使用。
---------------------------------------------------------
当程序执行到语句块或者函数末尾的右花括号时,系统回收内存(栈内存)。
---------------------------------------------------------
编译器:
1、执行预处理指令
2、把cpp文件编译成.o 文件;
3、把.o文件链接一个可执行文件。
---------------------------------------------------------
template <class T> //也可以用template <typename T >
void foo (T param)
{
//do something
}
在尖括号里有一个 class T, 用来告诉编译器,字母T将在接下来的函数里代表一种不确定的数据类型
关键词 class并不意味着这个是个类, 这只是一种约定俗成的写法。
注意: 不要把函数模板分成原型和实现两个部分。如果编译器看不到模板的完整代码, 将会报乱七八糟的错误。
---------------------------------------------------------
template <T>
swap (T &a, T &b)
{
}
1、可以通过变量推演出,真正的T类型
2、明确指出, swap<int>(a, b);
如果类型不一样, 操作是一样的,则使用模板。
如果类型不一样, 操作不同, 则可进行重载。
---------------------------------------------------------
类模板:
template <class T>
class MyClass{
MyClass();
void swap(T &a, T &b);
};
构造器的实现:
MyClass<T>:: MyClass()
{
//todo
}
因为MyClass是一个类模板, 所以不能只写MyClass::MyClass(),编译器需要你在这里给出一个与MyClass()配合使用的数据类型,必须在尖括号里提供它。因为没有确定的数据类型可以提供,所以使用一个T作为占位符即可。
自己看菜鸟教程总结: 不光是构造函数, 模板类中的成员方法(函数), 在定义时都要<T>.
---------------------------------------------------------
内联函数:
引入内联函数式为了解决程序中函数调用的效率问题。
内联函数从源代码层看, 有函数结构,而在编译后,却不具备函数的性质, 编译时类似宏替换。
使用函数体替换调用处的函数名。
一般在代码中用inline修饰,但是否行成内联函数,需要看编译器对该函数定义的具体处理。
不管是什么模板,编译器都必须看到全部的代码才能为一种给定的类型创建出一个新的实现来,
在创建类模板时,避免类声明和类定义相分离的一个好办法是使用内联方法。
---------------------------------------------------------
能容纳两个或更多个值的数据结构,通常我们称为容器(container)。
比如: 数组
std::vector<type> vectorName;
vectorName.size();//查向量的当前长度
vectorName.push_back() ;//往里面添加东西
#include <vector>
std::vector<std::string> names;
names.push_back(“小甲鱼”);
for(int i = 0; i<names.sizeof(); i++)
{
std::cout << names[i] <<endl;
}
把一个元素放到一个向量里以后, 就可以用赋值操作符来改变它们的值了, 就像对待数组元素那样:
names[0] = "Jonny";
迭代器(interator):是个所谓的智能指针, 具有遍历复杂数据结构的功能。
因为迭代器功能是如此的基本, 所以标准库里的每一种容器都支持。
通过使用迭代器,当在程序里改用另一种容器的时候就用不着修改那么多的代码了。
std::vector<std::string>::iterator iter = names.begin();
while(iter != names.end())
{
std::cout << iter <<endl;
++iter;
}
迭代器的真正价值,体现在它们可以和所有的容器配合使用, 而使用迭代器去访问容器元素的算法可以和任何一种容器配合使用。
#include <algorithm>
std:: sort(beginIterator, endIterator);
例子:
std::vector<std::string> names;
names.push_back(“小甲鱼”);
names.push_back(“Lilei”);
names.push_back(“Lucy”);
std::sort(names.begin(), names.end());