学习C++之前建议要有下列基础:
C++是对C语言的扩充,C++多出来的语法都是为了弥补C语言的不足,只有熟悉C语言,并且在编写C语言代码中发现C语言的不方便,学C++的时候才有相见恨晚,大呼过瘾的感觉。
C++将常用的数据结构以标准库的形式提供给我们了,如果你有数据结构的基础,当你看到C++中的vector容器、string容器、list容器的时候,你就会惊呼,这不就是数据结构里面的内容么,所以这部分内容只需要大概扫一眼就行了,因为它们该有什么功能、能干什么、有什么特性,你都已经了然于胸。
此外你需要少许汇编语言的知识,因为理解static变量的时候需要用到数据段、代码段、栈段和堆的概念,不过这不是必须的,这部分知识现学也是可以的。
#include <iostream>
int main(int argc, char const *argv[])
{
std::cout << "Hello World!";
return 0;
}
Hello World!
上面程序中std::cout
的意思是使用std命名空间中的cout(console output)。
命名空间可以防止名字重复。也可以在程序头部使用using,这样在程序中再使用就不必每次都指定命名空间了。
#include <iostream>
using std::cout;
int main(int argc, char const *argv[])
{
int a=123;
float b =3.14;
char c ='s';
cout << a;
cout << b;
cout << c;
return 0;
}
1233.14s
上面的程序没有换行,std::endl
可以输出换行符,相当于\n
,当我们需要引入多个std类里的静态成员时,可以直接设置using namespace std;
:
#include <iostream>
using namespace std;
int main(int argc, char const *argv[])
{
int a=123;
float b =3.14;
char c ='s';
cout << a << endl;
cout << b << endl;
cout << c << endl;
return 0;
}
123
3.14
s
就是给变量起了另外一个名字
int ival = 1024;
int &refVal = ival;
函数调用是有开销的,小型的函数可以让编译器直接内联到使用的代码中,相当于把小型函数里的内容直接写到使用的地方。这样会省去函数调用的开销。
定义常量,#define宏定义只是字符串替换,const是直接定义常量
const int bufSize = 512;
const修饰指针的时候比较容易弄混
int *const p1 // p1本身是常量,也叫常量指针
const int *p2 // p2是指向常量的指针
const int *const p3 // 常量指针指向常量
非常量指针不能指向常量
const double pi = 3.14;
double *ptr = π // 错误,ptr是非常量指针,不能指向常量
const double *cptr = π // 正确
大小可变的数组
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char const *argv[])
{
vector<int> ivec;
ivec.push_back(1);
ivec.push_back(2);
ivec.push_back(4);
ivec.push_back(6);
for(int i=0;i<ivec.size();i++)
{
cout<<ivec[i]<<",";
}
return 0;
}
1,2,4,6,
相当于vector<char>
,不过该容器多了对字符串的比较、替换等函数
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char const *argv[])
{
string s1 = "-----------";
string s2 = "***********";
// 截取字符串
cout << s1.substr(0, 5) << endl;
// 字符串拼接
s1.insert(3, s2);
cout << s1 << endl;
return 0;
}
-----
---***********--------
双向链表
#include <iostream>
#include <list>
using namespace std;
int main(int argc, char const *argv[])
{
list<int> ilist;
ilist.insert(ilist.begin(),1);
ilist.insert(ilist.begin(),2);
ilist.insert(ilist.begin(),4);
ilist.insert(ilist.begin(),6);
for (list<int>::iterator ite = ilist.begin(); ite != ilist.end(); ite++){
cout << *ite <<endl;
}
return 0;
}
6
4
2
1
容器只提供了基础的功能,一些复杂的算法被放在algorithm头文件中。这些算法是通用的:可用于不同类型的容器和不同类型的元素。内容很多,包扩各种排序算法,二分查找算法等,读者可以自行查阅:
https://www.cplusplus.com/reference/algorithm/
使用class和struct定义类唯一的区别就是默认的访问权限。
#include <iostream>
class Foo {
// 非成员函数想操作私有变量,可以通过友元实现。
friend void printa(const Foo&);
public:
// 这里的const是修改隐式this指针的类型
// 默认情况,this的类型Sale_data *const,加上const后变为const Sale_data *const
// 这么做之后成员函数不能改变this的内容
double getprice() const {
return price;
}
// 成员函数可以在类内定义,也可以在类内声明,在类外定义
double priceplus();
private:
double price;
};
// 在类外部定义成员函数时要加上Foo::指明该函数的作用域
double Foo::priceplus()
{
return ++price;
}
void printa(const Foo &item)
{
std::cout << item.price << std::endl;
}
int main(int argc, char const *argv[])
{
Foo F1;
F1.priceplus();
F1.priceplus();
F1.priceplus();
printa(F1);
return 0;
}
3
内存的分配方式有三种:
在堆中分配内存有不受栈大小的限制、多个线程可以共享数据等优点。缺点是在堆中分配的内存必须手动释放,否则会造成内存溢出。
对于一个简单的、运行时间很短、且堆空间足够用的程序而言,不会造成什么问题,在其进程结束时,操作系统会回收相关内存。但如果长期运行的服务器程序存在内存泄露,则会造成内存分配不成功, 由此引起致命错误。在引入请求分页机制的操作系统中,内存泄露实际上指的是进程的虚拟地址空间已经完全被分配完了。是的,物理内存很宝贵,但内存虚地址空间也是一种宝贵的资源。
class Foo{
int a;
};
int main(int argc, char const *argv[])
{
Foo F1; // 在栈中分配内存
Foo *F2 = new Foo; // 在堆中分配内存
delete F2; // 释放内存
return 0;
}
#include <iostream>
class Foo {
public:
// 构造函数,还有一种等价的写法
// Foo(double p,int n):price(p),num(n){}
Foo(double p,int n){
price = p;
num = n;
}
// 构造函数可以有多个,但只调用对应参数的那一个
void print() const {
std::cout << price << ","<< num << std::endl;
}
private:
double price;
int num;
};
int main(int argc, char const *argv[])
{
Foo F1(100,6);
F1.print();
return 0;
}
100,6
#include <iostream>
class Foo {
public:
// 当类被销毁的时候会自动调用该函数
~Foo(){
std::cout << "~Foo()"<< std::endl;
}
};
int main(int argc, char const *argv[])
{
Foo *F2 = new Foo; // 在堆中分配内存
delete F2; // 释放内存
return 0;
}
~Foo()
静态变量与全局变量都存在数据段中,他们只有作用域的区别:
同样的,静态函数也是一样的道理。
因为静态变量是存在数据段中的,静态函数是存在代码段中的,所以类中所定义的静态变量和静态函数无需实例化即可使用。但要注意,静态变量使用前必须初始化,这点和类中的其他成员不一样。
#include <iostream>
class Foo
{
public:
Foo()
{
count++;
}
~Foo()
{
count--;
}
static void output()
{
std::cout << count << std::endl;
}
private:
static int count;
};
// 静态变量使用前必须初始化.
int Foo::count = 666;
int main()
{
// 静态函数没实例化也可以直接调用,因为它是存储在代码段的
Foo::output();
Foo *p = new Foo; //
p->output();
delete p;
Foo::output();
return 0;
}
666
667
666
#include <iostream>
using namespace std;
class Foo{
public:
int num;
// 重载 + 运算符,用于把两个 Box 对象相加
int operator+(const Foo& a)
{
return this->num+a.num;
}
};
int main()
{
Foo F1;
F1.num = 1;
Foo F2;
F2.num = 2;
cout << F1+F2;
return 0;
}
3
#include <iostream>
using namespace std;
struct Array{
Array(int length): m_length(length){
m_p = new int[length];
for(int i = 0; i < length; i++){
m_p[i] = i * 5;
}
};
~Array(){
delete[] m_p;
};
int & operator[](int i){
return m_p[i];
};
int m_length; //数组长度
int *m_p; //指向数组内存的指针
};
int main()
{
Array A(10);
for(int i = 0; i < A.m_length; i++){
cout<<A[i]<<",";
}
cout<<endl;
return 0;
}
0,5,10,15,20,25,30,35,40,45,
#include <iostream>
using namespace std;
template<typename T> void Swap(T &a, T &b){
T temp = a;
a = b;
b = temp;
}
int main(int argc, char const *argv[])
{
int n1 = 100, n2 = 200;
Swap(n1, n2);
cout << n1 <<", " << n2 <<endl;
float f1 = 12.5, f2 = 56.93;
Swap(f1, f2);
cout << f1 <<", "<< f2 << endl;
char c1 = 'A', c2 = 'B';
Swap(c1, c2);
cout << c1<<", "<< c2 <<endl;
return 0;
}
200, 100
56.93, 12.5
B, A
#include <iostream>
using namespace std;
class Father
{
public:
void fun()
{
std::cout << "Father call function!" << std::endl;
}
};
class Son : public Father
{
};
int main()
{
Son * son = new Son;
son->fun();
return 0;
}
Father call function!
继承的优点之一是它支持渐增式开发,它允许我们在已存在的代码中引进新代码,而不会给原代码带来错误,即使产生了错误,这个错误也只与新代码有关。
来看一段代码:
enum note {middleC,Csharp,Cflat};
class instrument{
public:
void play(note) const {}
};
class wind : public instrument {};
void tune(instrument& i){
i.play(middleC);
}
int main(){
wind flute;
tune(flute);
return 0;
}
tune函数的实参是instrument类型的,但是传入wind类型居然不出错。
仔细一想wind继承了instrument,说明wind也是instrument的一种呀,不算错也合理。这种将wind的对象、引用或指针转变成instrument对象、引用或指针的活动称为向上映射。
向上映射让修改代码变得容易,例如我们对instrument不满意,于是继承下来做了一番修改,修改后的类叫做wind。如果没有向上映射功能的化,所有涉及到instrument的函数都要做修改,但是有了向上映射功能后,原本处理instrument的函数,仍然可以用来处理wind,无需修改。
但是向上映射功能引入了一个新的问题:
# include <iostream>
using namespace std;
enum note {middleC,Csharp,Cflat};
class instrument{
public:
void play(note) const {
cout << "instrument::play" <<endl;
}
};
class wind : public instrument {
public:
void play(note) const {
cout << "wind::play" <<endl;
}
};
void tune(instrument& i){
i.play(middleC);
}
int main(){
wind flute;
tune(flute);
return 0;
}
instrument::play
上面的函数我们传入的是wind类型的数据,但是由于进行了向上映射,被转换成了instrument类型,所以没有调用wind类中的函数,而是调用了instrument类中的函数。这显然与我们的意图不符。
为了解决这个问题,引入虚函数这一概念:
# include <iostream>
using namespace std;
enum note {middleC,Csharp,Cflat};
class instrument{
public:
virtual void play(note) const {
cout << "instrument::play" <<endl;
}
};
class wind : public instrument {
public:
void play(note) const {
cout << "wind::play" <<endl;
}
};
void tune(instrument& i){
i.play(middleC);
}
int main(){
wind flute;
tune(flute);
return 0;
}
wind::play
virtual实现play函数晚捆绑,即运行时确定对象的类型和合适的调用函数。同样调用play函数,因为参数不同,函数的处理逻辑完全不同,这就是所谓的多态。
如果一个函数在基类中被声明为virtual,那么在所有的派生类中它都是virtual的。
我们可能会有这样一个疑问:“如果这个技术如此重要, 并且能使得任何时候都能调用“正确”的函数。那么为什么它是可选的呢?为什么不默认就是虚函数呢?”
答:因为它会影响效率。
有的时候,我们想为某几个功能统一接口,他们的内部实现我们不管,但是给外部调用的接口要一致。
这时我们可以定义一个抽象基类,把统一的接口以纯虚函数的形式先定义好。所有的类都要继承这个抽象基类。
抽象基类本身是不可以实例化的,子类继承抽象基类后必须实现纯虚函数:
class base {
public:
virtual void v() const = 0;
};
class d: public base {
};
int main(int argc, char const *argv[])
{
base B; // 错误,有纯虚函数的类不能实例化
d D; // 错误,子类继承有纯虚函数的类后,必须实现该函数
return 0;
}
构造函数不能是虚的。析构函数能够且常常必须是虚的。
虚析构函数会调用父类的析构函数,否则不会调用父类的析构函数。
# include <iostream>
using namespace std;
class base {
public:
virtual ~base() {
cout << "~base()" << endl;
}
};
class derived: public base {
public:
~derived() {
cout << "~derived()" << endl;
}
};
int main(int argc, char const *argv[])
{
base * bp = new derived;
delete bp;
return 0;
}
以上就列举C++的主要特性,可以快速上手C++以形成生产力。可以看出C++相对C语言最主要的特征是多了类和泛型,这两个可是提升编码效率的大杀器。
C++还有很多语法,他们不太常用,而且不是C++的核心,所以用到的时候现查字典吧。