Hello World!
在学C++之前,最好先学习一下C语言
让我们先运行一段简单的代码,编译器可以使用 在线C++编译器 或 Xcode(苹果系统) 或Dev C++(Windows系统)。
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!" << endl;
return 0;
}
运行结果:
Hello World!
接下来我们讲解一下上面这段程序:
(1)C++ 语言定义了一些头文件,这些头文件包含了程序中必需的或有用的信息。上面这段程序中,包含了头文件 <iostream>。
(2)下一行 using namespace std; 告诉编译器使用 std 命名空间。命名空间是 C++ 中一个相对新的概念。
什么是命名空间呢?
在C++中,名称(name)可以是符号常量、变量、宏、函数、结构、枚举、类和对象等等。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入了关键字namespace(命名空间/名字空间/名称空间/名域),可以更好地控制标识符的作用域。
做个比喻,假如三年级(一)班有个小朋友叫Teodoro,三年级(二)班有个小朋友也叫Teodoro。
在(一)班内部说Teodoro时,大家都知道说的是本班的Teodoro。也就是说,(一)班的Teodoro,其作用域是(一)班。
同理,(二)班的Teodoro,其作用域是(二)班。
那如果是校长要找Teodoro呢?因为有两个叫Teodoro的小朋友,校长需要把班级名称都说出来,比如“我找(一)班的Teodoro”,这样才能明确地指出要找的是哪个Teodoro。(一)班或(二)班,就叫做命名空间。
(3)下一行 int main() 是主函数,程序从这里开始执行。
(4)下一行 cout << "Hello World"; 会在屏幕上显示消息 "Hello World"。
(5)下一行 return 0; 终止 main( )函数,并向调用进程返回值 0。
面向过程与面向对象
先用C++编写一个加法程序
#include <iostream>
using namespace std;
int add(int a, int b)
{
return a + b;
}
int main()
{
int x = 5, y = 10;
int z = add(5, 10);
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "x + y = " << z << endl;
return 0;
}
运行结果:
x = 5
y = 10
x + y = 15
从上一节的Hello World程序和本节的加法程序来看,C和C++貌似看起来差不多,只是修改了一些头文件和语法而已。那么,二者真的高度相似吗?
答案是否定的!C和C++差别非常大。
C是面向过程的。
C++既可以面向过程,也可以面向对象,并且以面向对象为主。
为何要以面向对象为主呢?因为若只是面向过程,用C语言就行了,不用再劳心劳力再创造一种新的语言。C++是一些聪明的程序员在C的基础上创造、发展起来的,与C语言最大的区别就是面向对象。
C语言的重点在于算法和数据结构,C程序的设计首要考虑的是如何通过一个过程,对输入进行运算处理得到输出。
所以c语言是面向过程语言。
而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程控制。
从这一点看,C++是面向对象编程(OOP)的。
C++还像C语言一样面向过程。
所以C++既是面向过程的语言 ,也是面向对象的语言。一般来说,用它的面向对象这方面。
以后会学到Java。Java不能面向过程,是完全面向对象的。
以狗吃屎为例,来说明面向过程和面向过对象的区别。
C语言:吃(狗,屎)
这里“吃”是函数名,“狗”和“屎”是参数。强调的是吃这一过程。
C++:狗.吃屎()
这里狗是对象,吃屎是狗的一个函数。语法是一个完整的主谓宾结构。主语是“狗”,谓语是“吃”,宾语是“屎”。
狗除了吃屎外,还有其他行为,比如汪汪叫,伸舌头等。语法为
狗.汪汪叫()
狗.伸舌头()
这一系列行为里,强调的是“谁来做”,这里是狗来做,所以强调的是狗这一对象。
类与对象
(一)类与对象
类是由我们根据客观事物抽象而成,形成一类事物,然后用类去定义对象,形成这类事物的具体个体。
比如小狗是一个类,你家的“旺财”则是小狗一个具体的对象。
(二)属性与方法
一般把类的数据成员称为类的属性,把类的函数成员称为方法。
比如小狗这个类吧,它的属性有身高、体长、体重、名字、年龄、性别等,它的方法有吃,走,跑,呼吸,吠等。
从这里也可以看出,属性都是静态的,而方法都是动作。
(三)程序
#include <iostream>
using namespace std;
class Dog
{
public:
string name; // 名字
int age; // 年龄
int sex; // 性别,可以定义为,1表示公,0表示母
float height; // 身高
float length; // 体长
float weight; // 体重
void eat()
{
cout << "eating..." << endl;
}
void walk()
{
cout << "walking..." << endl;
}
void run()
{
cout << "running..." << endl;
}
void breathe()
{
cout << "breathing..." << endl;
}
void bark()
{
cout << "wang! wang!" << endl;
}
};
int main()
{
Dog dog;
dog.name = "Wang Cai";
dog.age = 3;
dog.run();
dog.bark();
return 0;
}
运行结果:
running...
wang! wang!
(四)程序分析
(1)在类定义结尾处的}后面需要加分号,这是语法要求。否则编程出错。
(2)public表示公有的,在类的外部可以访问。main()函数就属于类的外部。
(3)Dog dog; 这是声明一个类型为Dog的对象dog。也可以写为
Dog dog1;
Dog mydog;
Dog myDog;
按照惯例,对象的首字母建议小写。
(4)dog.name=xxx; 这种赋值的写法是给对象设置属性。
(5)dog.run(); 这种写法是调用对象的方法。
public, protected, private
面向对象有三大特征:封装、继承、多态。(具体会在之后的课程里讲)
C++用三个关键词来表示访问范围:public, protected, private。
public和private作用就是实现封装。类外的代码可以访问public成员而不能访问private成员;private成员只能由类成员访问。
protected的作用则是实现继承。protected成员可以被派生类(也叫子类)对象访问,不能被用户代码类外的代码访问。
例1:private修饰属性和方法
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
void run()
{
cout << "running..." << endl;
}
};
int main()
{
Dog dog;
dog.name = "Wang Cai";
dog.run();
return 0;
}
编译程序,报错:
这是因为,name和run()都是私有的,类外的main()没有权限访问dog.name和dog.run()
例2:将private改为protected
#include <iostream>
using namespace std;
class Dog
{
protected:
String name;
void run()
{
cout << "running..." << endl;
}
};
int main()
{
Dog dog;
dog.name = "Wang Cai";
dog.run();
return 0;
}
编译程序,报错:
报错理由跟上面的差不多,因为name和run()是protected, 外部的main()没有权限访问。
例3:将protected改为public
#include <iostream>
using namespace std;
class Dog
{
public:
string name;
void run()
{
cout << "running..." << endl;
}
};
int main()
{
Dog dog;
dog.name = "Wang Cai";
dog.run();
return 0;
}
程序正常运行。运行结果:
running...
有些人可能会想,我不加任何关键字,后果会怎样?
例4:不加修饰符
#include <iostream>
using namespace std;
class Dog
{
string name;
void run()
{
cout << "running..." << endl;
}
};
int main()
{
Dog dog;
dog.name = "Wang Cai";
dog.run();
return 0;
}
编译报错,错误与例1相同:
可见如果不加任何关键字,无论是属性还是方法,默认都是private。
构造函数
构造函数,作用是完成对象的初始化工作。
可类比于:int a = 1;这里是给变量a赋初值。
构造函数是一种特殊的函数,首先构造函数名与类名是完全一致的,其次构造函数没有类型。
构造函数可以不带参数,也可以带参数。
#include <iostream>
using namespace std;
class Dog
{
public:
string name;
// 无参构造函数
Dog()
{
cout << "Dog's constructor!" << endl;
}
// 有参构造函数
Dog(string Name)
{
name = Name;
cout << "Dog's constructor with name!" << endl;
}
void run()
{
cout << name << " is running..." << endl;
}
};
int main()
{
Dog dog1;
dog1.name = "Wang Cai";
dog1.run();
Dog dog2("Xiao Bai");
dog2.run();
return 0;
}
运行结果:
Dog’s constructor!
Wang Cai is running...
Dog’s constructor with name!
Xiao Bai is running
从运行结果可以看出,构造函数是在生成对象时被调用的,并且不需要显示调用。
this指针
this指针是一个隐含于类中的特殊指针,指向对象本身。也就是说对象一旦被创建,this指针也就存在了。
就好比你的名字叫做Teodoro,别人说你的时候用的是Teodoro,但是你说你自己的时候,用的是“我”。
这个“我”,在C++和Java中,是用this来表示的。而在Python和Objective-C(苹果的开发语言)中,则用self来表示。
程序1
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
public:
Dog(string Name)
{
name = Name;
cout << "Constructor method with name!" << endl;
}
void run()
{
cout << name << " is running!" << endl;
}
};
int main()
{
Dog dog("Wang Cai");
dog.run();
return 0;
}
运行结果:
Constructor method with name!
Wang Cai is running!
分析:
在构造函数里,Name为形参,实参为main()函数中的“Wang Cai”。
通过name = Name赋值后,dog的属性name就有了值“Wang Cai”。
这样在run()函数中,就可以打印出属性name的值出来。
对程序1稍作改动,将构造函数的形参改为name,与类的私有属性name一样
程序2
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
public:
Dog(string name)
{
name = name;
cout << "Constructor method with name!" << endl;
}
void run()
{
cout << name << " is running!" << endl;
}
};
int main()
{
Dog dog("Wang Cai");
dog.run();
return 0;
}
运行结果:
Constructor method with name!
is running!
分析:
类Dog有一个属性为name,其作用域为整个类,相当于这个类的全局变量。
构造函数的形参为name,其作用域为构造函数内部,是一个局部变量。
name = name; 这个语句发生在构造函数内部,因为局部变量会屏蔽全局变量,两个name指的都是形参name。所以这个语句就相当于形参name给自己赋值。这样类的属性name没有被赋值,一直为空。
在执行dog.run()时 ,因为name为空,所以打印出来的就是空值。
如果构造函数的参数名称与类的属性名称一样,可以显示调用this来加以区分
程序3
#include <iostream>
using namespace std;
class Dog
{
private:
string name;
public:
Dog(string name)
{
this->name = name;
printf("%p
", &this->name);
printf("%p
", &name);
cout << "Constructor method with name!" << endl;
}
void run()
{
cout << name << " is running!" << endl;
printf("%p
", &name);
}
};
int main()
{
Dog dog("Wang Cai");
dog.run();
return 0;
}
运行结果:
000000000022fe20
000000000022fe30
Constructor method with name!
Wang Cai is running!
000000000022fe20
分析:
构造函数里的语句为this->name = name; 从打印的内存地址就可以看出,this->name与name不是同一回事。
等号左边的this->name为对象的属性,等号右边的name则为构造函数的形参,具体由main()函数中的实参“Wang Cai”赋值。
这个语句的效果为this-> name = name = “Wang Cai”
执行run()时,这个name肯定只能是类的属性了,而不可能是构造函数的形参。这从打印出来的内存地址可以看出来。
实际上,run()中的name完全等价于this->name,只是this不用显示写出来罢了。当然要显示写出来也是可以的。
最后,看一个更简单的例子。
程序4
#include <iostream>
using namespace std;
class A
{
public:
A()
{
printf("Memory address of this: %p
", this);
}
};
int main()
{
A a;
printf("Memory address of a: %p
", &a);
return 0;
}
运行结果:
Memory address of this: 000000000022fe4f
Memory address of a: 000000000022fe4f
分析:
从运行结果可以看出,在类外部运行的对象的内存地址,与类内部运行的this的内存地址,完全一样。
这也印证了上面说的,别人口中的Teodoro与Teodoro自己口中的“我”,就是同一个人。
封装
面向对象有三个特征:封装、继承和多态。
本节主要讲解封装。
所有的 C++ 程序都有以下两个基本要素:
函数:这是程序中执行动作的部分,它们被称为函数或方法。
数据:数据是程序的信息,会受到程序函数的影响,也叫属性。
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。
我们已经知道,类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。默认情况下,所有的数据成员和函数成员都是私有的。
为了使类中的成员变成公有的(即程序中的类外部的其他部分也能访问),必须在这些成员前使用 public 关键字进行声明。所有定义在 public 标识符后边的属性或函数可以被程序中所有其他的函数访问。
例子:
#include <iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
};
private:
// 对外隐藏的数据
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total is " << a.getTotal() << endl;
return 0;
}
运行结果:
Total is 60
上面的类把数字相加,并返回总和。公有成员 addNum 和 getTotal 是对外的接口,用户需要知道它们以便使用类。私有成员 total 是对外隐藏的(即被封装起来),用户不需要了解它,但它又是类能正常工作所必需的。
类的设计策略:
通常而言,要把类的数据成员设计成私有(private),类的函数成员则根据实际需要设计成publice, protected或private。
继承
先编写程序:
#include <iostream>
using namespace std;
class Animal
{
protected:
float weight;
public:
void setWeight(float w)
{
weight = w;
}
float getWeight()
{
return weight;
}
void breathe()
{
cout << "breathing..." << endl;
}
};
class Dog : public Animal
{
private:
int legs;
public:
void setLegs(int number)
{
legs = number;
}
int getLegs()
{
return legs;
}
void bark()
{
cout << "wang! wang!" << endl;
}
};
int main(int argc, char** argv)
{
Dog dog;
dog.setWeight(12.5);
dog.setLegs(4);
cout << "The dog's weight is " << dog.getWeight() << "kg" << endl;
cout << "The dog has " << dog.getLegs() << " legs" << endl;
dog.breathe();
dog.bark();
return 0;
}
运行结果:
The dog’s weight is 12.5kg
The dog has 4 legs
breathing...
wang! wang!
程序分析:
(1)Animal为一个类,Dog为另一个类。
class Dog : public Animal 表示Dog类继承了 Animal类。此时,Animal就成了Dog的基类或父类。Dog就成了Animal的派生类或子类。
(2)体重和呼吸是所有动物的共性,所以weight和breathe()定义在类Animal中。腿和吠不是所有动物的共性,所以legs和bark()定义在了类Dog中。
(3)class Dog : public Animal , 这里的public表示继承方式 。
继承方式有三种:public, protected和private。
① 父类为private的属性和方法,不能被子类继承。
② 父类为protected的成员,若被子类public继承的,仍为子类的protected成员;若被子类protected或private继承的,变为子类的private成员。
③ 父类为public的成员,若被子类public继承的,仍为子类的public成员;若被子类protected继承的,变为子类的protected成员;若被子类private继承的,变为子类的private成员。
注:这些不用强记,编多了自然就知道
(4)根据(3)中第③条的结论,若程序改为class Dog : protected Animal,则程序无法通过编译。因为setWeight()和getWeight()被protected继承后,变为Dog的protected成员,而protected成员,无法在类外部(比如main函数中)访问。
构造函数的默认参数
构造函数可以预先赋一个初值,其作用是:在构造函数被调用时,省略部分或全部参数,这时就会使用默认参数代替实参。
程序:
#include <iostream>
using namespace std;
class Rectangle
{
private:
int width;
int height;
public:
Rectangle(int w = 0, int h = 0)
{
cout << "Constructor method is invoked!" << endl;
width = w;
height = h;
}
int area()
{
return width * height;
}
};
int main(int argc, char** argv)
{
Rectangle rec1;
cout << "Area of rec1 is : " << rec1.area() << endl;
Rectangle rec2(5);
cout << "Area of rec2 is : " << rec2.area() << endl;
Rectangle rec3(5, 10);
cout << "Area of rec3 is : " << rec3.area() << endl;
return 0;
}
运行结果:
Constructor method is invoked!
Area of rec1 is : 0
Constructor method is invoked!
Area of rec2 is : 0
Constructor method is invoked!
Area of rec3 is : 50
分析:
生成对象rec1时,没有传入拷贝构造函数的实参,则形参w和h取默认值0
w = 0, h = 0
在构造函数中,weight = w = 0, height = h = 0
在area函数中, weight * height = 0
生成对象rec2时,传入实参5,相当于传入(5,0),则w = 5, h = 0
在构造函数中,weight = w = 5, height = h = 0
在area函数中,weight * height = 0
生成对象rec3时,传入实参(5,10),则w = 5, h = 10
在构造函数中, weight = w = 5, height = h = 10
在area函数中,weight * height = 50
子类构造函数调用父类构造函数
从哲学层面来看,子类会继承父类除private以外的所有成员。
因为构造函数是公有的,所以理所当然地会被子类继承。
程序1:
#include <iostream>
using namespace std;
class Shape
{
public:
Shape()
{
cout << "Shape's constructor method is invoked!
";
}
};
class Rectangle : public Shape
{
public:
Rectangle() : Shape()
{
cout << "Rectangle's constructor method is invoked!
" << endl;
}
};
int main(int argc, char** argv)
{
Rectangle rec;
return 0;
}
运行结果:
Shape's constructor method is invoked!
Rectangle's constructor method is invoked!
分析:
这里构造函数的写法是
Rectangle() : Shape()
{
子类构造函数本身的语句;
}
这是先调用父类的构造函数,再执行它本身的语句。从运行结果也可以看出这一点。
那么,如果不显示调用父类的构造函数Shape()呢?父类的构造函数就不被调用了吗?
咱们可以用下面的程序来验证。
程序2:
#include <iostream>
using namespace std;
class Shape
{
public:
Shape()
{
cout << "Shape's constructor method is invoked!
";
}
};
class Rectangle : public Shape
{
public:
Rectangle()
{
cout << "Rectangle's constructor method is invoked!
" << endl;
}
};
int main(int argc, char** argv)
{
Rectangle rec;
return 0;
}
运行结果:
Shape's constructor method is invoked!
Rectangle's constructor method is invoked!
分析:
从运行结果可以看出,程序1和程序2的运行结果完全一致。也就是说,Shape()即使不显示调用,实际上也会被调用。并且调用顺序优先于子类本身的构造函数。
“箭头(->)”和“点号(.)”操作符的区别
先看一个程序:
#include <iostream>
using namespace std;
class A
{
public:
void play()
{
cout << "playing..." << endl;
}
};
int main()
{
A a;
a.play();
A *p = &a;
(*p).play();
p->play();
return 0;
}
运行结果:
playing...
playing...
playing...
结论:
在C++中,
若是普通对象,使用点号操作符;
若是指针对象,有两种操作方式:
(*指针).方法() (1)
指针-->方法() (2)
但是(1)不常用,所以(2)中的箭头操作符用的比较多。