构造函数
析构函数
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
(C++ 初始化类成员时,是按照声明的顺序初始化的,而不是按照出现在初始化列表中的顺序)
所以初始化顺序最好要按照变量在类声明的顺序一致。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明,默认
Line(double len); // 这是构造函数,带参数
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
Line::Line( double len)//带参数的构造函数初始化
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
指针*(指向地址),引用&(取地址,引用更接近const指针,必须在创建时进行初始化),
简单点就是:&:取址。* ,取值,数组名是指针,
指针的本质是变量,可以是各种数据类型,定义一个指针 “*ip”,其中 “ip” 需要赋于一个地址(可以用 & 符号获取其他变量的地址再赋值给 ip),而 “*ip” 是一个具体的值,即读取地址后获得的值;
实例代码:
#include <iostream>
using namespace std;
int main()
{
int var = 20;
int *ip;
ip = &var;
cout << "var的值:";
cout << var << endl;
cout << "变量 ip 的储存地址:";
cout << ip << endl;
cout << "指针 *ip 的值:";
cout << *ip << endl;
return 0;
}
/*var的值:20
变量 ip 的储存地址:0x7fff5e7deae8
指针 *ip 的值:20*/
多态
形成多态必须具备三个条件:
1、必须存在继承关系;
2、继承关系必须有同名虚函数(其中虚函数是在基类中使用关键字Virtual声明的函数,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数);
3、存在基类类型的指针或者引用,通过该指针或引用调用虚函数;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
结构体( 数据结构)
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;//定义一个结构体类型 Books,声明一个变量为 book
类(类中同名函数为构造函数)、继承、虚函数()、纯虚函数( virtual int area() = 0;)
访问修饰符 access-specifier 是 public、protected 或 private 其中的一个,base-class 是之前定义过的某个类的名称。如果未使用访问修饰符 access-specifier,则默认为 private。
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
重载运算符和重载函数【在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。您不能仅通过返回类型的不同来重载函数。】
#include <iostream>
using namespace std;
class printData
{
public:
void print(int i) {
cout << "整数为: " << i << endl;
}
void print(double f) {
cout << "浮点数为: " << f << endl;
}
void print(char c[]) {
cout << "字符串为: " << c << endl;
}
};
int main(void)
{
printData pd;
// 输出整数
pd.print(5);
// 输出浮点数
pd.print(500.263);
// 输出字符串
char c[] = "Hello C++";
pd.print(c);
return 0;
}
模板typename
// 函数模板
template <typename type> ret-type func-name(parameter list)
{
// 函数的主体
}
// 类模板
template <class type> class class-name {
.
.
.
}
#include <iostream>
using namespace std;
class Box
{
public:
double getVolume(void)
{
return length * breadth * height;
}
void setLength( double len )
{
length = len;
}
void setBreadth( double bre )
{
breadth = bre;
}
void setHeight( double hei )
{
height = hei;
}
// 重载 + 运算符,用于把两个 Box 对象相加
Box operator+(const Box& b)
{
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->height + b.height;
return box;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 把体积存储在该变量中
// Box1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// Box2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// Box1 的体积
volume = Box1.getVolume();
cout << "Volume of Box1 : " << volume <<endl;
// Box2 的体积
volume = Box2.getVolume();
cout << "Volume of Box2 : " << volume <<endl;
// 把两个对象相加,得到 Box3
Box3 = Box1 + Box2;
// Box3 的体积
volume = Box3.getVolume();
cout << "Volume of Box3 : " << volume <<endl;
return 0;
}
命令空间(作用域A::B,当前作用域::a)
namespace namespace_name {
// 代码声明
}
using namespace namespace_name;
namespace_name::方法()
namespace_name::变量
- 全局变量和和局部变量同名时,可通过域名在函数中引用到全局变量,不加域名解析则引用局部变量
#include<iostream>using namespace std;
int a = 10;int main(){
int a = 20;
cout << ::a << endl; // 10
cout << a << endl; // 20
return 0;
}
- 定义成 const 后的常量,程序对其中只能读不能修改。
以下程序是错误的,因为开头就已经固定了常量,便不能再对其进行赋值:
#include <iostream>
using namespace std;
int main()
{
const double pi; //圆周率的值用pi表示
pi=3.14159265;
cout<<"圆周率的近似值是"<<pi<<endl;
return 0;
}
下面给出正确的赋值方法:
#include <iostream>
using namespace std;
int main()
{
const double pi=3.141592; //圆周率的值用pi表示
cout<<"圆周率的近似值是"<<pi<<endl;
return 0;
}
- 预处理 #define 变量定义值以后,不能用分号,否则就会计算错误,但是程序不会报错。
#define age 12
#define age1 10
#define age2 12;
#define age3 10;
int main()
{
int dd ;
dd = age + age1;
cout << "值=" << dd << endl; //值22
dd = age2 + age3;
cout << "值=" << dd << endl; //值12
return 0;
}
指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。
指针运算符 * 指向一个变量。例如,*var; 将指向变量 var。
- 运算符优先级助记歌(自己编的,优先级从高到低):
先算右一后左一,①
乘除加减移位比。②
与异或或位逻辑,③
三目赋值逗号稀。④
①先算右面的一元运算符,后算左面的。
②乘除代表*/%,加减就是+和-,移位就是<<和>>,比就是比较运算符,注意比较运算符先算<、>、<=和>=这4个含不等号的,后算==和!=这两个。
③先算按位逻辑运算符,再算普通的逻辑运算符;按位逻辑运算符的顺序是&^|,逻辑运算符先算&&再算||,只是少了逻辑异或。
④先算三目运算符,再算赋值运算符,逗号运算符的优先级最低,所以说它“稀”。
- 两个数互换,使用异或
int swap(int& a, int& b)
{
int temp;
temp = a ^ b;
a = temp ^ a;
b = temp ^ b;
return 0;
}
Lambda 函数与表达式
[]:默认不捕获任何变量;
[=]:默认以值捕获所有变量;
[&]:默认以引用捕获所有变量;
[x]:仅以值捕获x,其它变量不捕获;
[&x]:仅以引用捕获x,其它变量不捕获;
[=, &x]:默认以值捕获所有变量,但是x是例外,通过引用捕获;
[&, x]:默认以引用捕获所有变量,但是x是例外,通过值捕获;
[this]:通过引用捕获当前对象(其实是复制指针);
[*this]:通过传值方式捕获当前对象;
- Array 直接初始化 char 数组是特殊的,这种初始化要记得字符是以一个 null 结尾的。
a4 是错误的,虽然 a4 包括 6 个直接字符,但是 array 大小是 7:6个字符 + 一个null。
char a1[] = {'C', '+', '+'}; // 初始化,没有 null
char a2[] = {'C', '+', '+', '\0'}; // 初始化,明确有 null
char a3[] = "C++"; // null 终止符自动添加
const char a4[6] = "runoob"; // 报错,没有 null 的位置
正确的是:
const char a4[7] = "runoob";
- Array 是固定大小的,不能额外增加元素.当我们想定义不固定大小的字符时,可以使用 vector(向量) 标准库。
指针和引用
- C++ 中使用指针
使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
#include <iostream>
using namespace std;
int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明
ip = &var; // 在指针变量中存储 var 的地址
cout << "Value of var variable: ";
cout << var << endl;
// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;
// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;
return 0;
}
- 指针指向数组的时候,不可以加 &:
指针指向数组中某一元素时要用 &
int main()
{
int var[5] = {1,2,3,4,5}; // 实际变量的声明
int *ip;
// 指针变量的声明
int *ip_1;
//指针指向数组的时候不用 “&”取址符
// 在指针变量中存储 var 的地址
ip = var;
//指针指向数组某一元素时要用 “&”取址符
// 在指针变量中存储 var[2] 的地址
ip_1 = &var[2];
return 0
}
- 引用,看做别名
引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。
试想变量名称是变量附属在内存位置中的标签,您可以把引用当成是变量附属在内存位置中的第二个标签。
把引用作为参数,C++ 支持把引用作为参数传给函数,这比传一般的参数更安全。
把引用作为返回值,可以从 C++ 函数中返回引用,就像返回其他数据类型一样。
int& r = i; 和 int r = i; 不同之处应该是内存的分配吧,后者会再开辟一个内存空间
- 数组的引用一定要表明数组的大小。
int a[] = { 1,2,3,4 };
int(&t)[4] = a;
- 引用int& var=var1 和 指针int *var的区别,取地址 int &var
引用很容易与指针混淆,它们之间有三个主要的不同:
不存在空引用。引用必须连接到一块合法的内存。
一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
引用必须在创建时被初始化。指针可以在任何时间被初始化。
- C++11 新增特性 – 右值引用
加入右值引用的目的主要是为了解决这样一个问题:在函数传值以及返回值的时候,一个对象被复制给另外一个对象,然后这个对象紧接着就被析构了。显然很浪费时间。使用右值引用的话就可以把一个对象的所有权“转让”给另外一个对象,而无需调用复制构造函数和析构函数。写法示例:
int &&rvalueReference = 1 + 2;
类与结构体
结构体(C与C++结构体中前者不能有函数,后者可以有。)
type_name 是结构体类型的名称,member_type1 member_name1 是标准的变量定义,比如 int i; 或者 float f; 或者其他有效的变量定义。在结构定义的末尾,最后一个分号之前,您可以指定一个或多个结构变量,这是可选的。下面是声明一个结构体类型 Books,变量为 book:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book(初始化了一个结构体,C++里面叫声明);
- 使用new动态创建结构体变量
使用new动态创建结构体变量时,必须是结构体指针类型。访问时,普通结构体变量使用使用成员变量访问符".“,指针类型的结构体变量使用的成员变量访问符为”->"。注意:动态创建结构体变量使用后勿忘delete。
- C++ 中的 struct 对 C 中的 struct
进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。
struct 能包含成员函数吗? 能!
struct 能继承吗? 能!!
struct 能实现多态吗? 能!!!
- C++中的struct和class基本是通用的,有几个不同之处:
使用 class 时,类中的成员默认都是 private 属性的;而使用 struct 时,结构体中的成员默认都是 public 属性的。
class 继承默认是 private 继承,而 struct 继承默认是 public 继承(《C++继承与派生》一章会讲解继承)。
class 可以使用模板,而 struct 不能(《模板、字符串和异常》一章会讲解模板)。
-
一个派生类继承了所有的基类方法,但下列情况除外:
基类的构造函数、析构函数和拷贝构造函数。
基类的重载运算符。
基类的友元函数。 -
继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access-specifier 来指定的。
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
私有继承(private): 当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
1、与类同名的函数是构造函数。
2、~ 类名的是类的析构函数。
- 运算重载符注意事项:
1、运算重载符不可以改变语法结构。
2、运算重载符不可以改变操作数的个数。
3、运算重载符不可以改变优先级。
4、运算重载符不可以改变结合性。
- 类重载、覆盖、重定义之间的区别:
重载 指的是函数具有的不同的参数列表,而函数名相同的函数。重载要求参数列表必须不同,比如参数的类型不同、参数的个数不同、参数的顺序不同。如果仅仅是函数的返回值不同是没办法重载的,因为重载要求参数列表必须不同。(发生在同一个类里)
覆盖 是存在类中,子类重写从基类继承过来的函数。被重写的函数不能是static的。必须是virtual的。但是函数名、返回值、参数列表都必须和基类相同(发生在基类和子类)
重定义也叫做隐藏重定义也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。(发生在基类和子类)
- 虚函数(virtual修饰符则强调父类的成员函数可以在子类中被重写)
父类的虚函数或纯虚函数在子类中依然是虚函数。有时我们并不希望父类的某个函数在子类中被重写,在 C++11 及以后可以用关键字 final 来避免该函数再次被重写。
- 类中的构造函数可以初始化类变量
#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 " << a.getTotal() <<endl;
return 0;
}
全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
自定义命名空间
以下代码会出错:会显示 a 变量和 fun 函数 “was not declared in this scope”,即找不到这个 a 和 fun 函数。
解决办法: 用 using 来告诉编译器用到的是哪个命名空间内的内容。在 main() 上面加 using namespace A; 或者 using namespace A::B; 。这样就可以使用其中的 a 和 fun()。但是不能同时使用,因为这样也会导致编译出错,编译器器不知道要去使用哪个 a 和 fun()。
#include <iostream>
using namespace std;
namespace A
{
int a = 100;
int fun()
{
cout<<"a = "<<a<<endl;
}
namespace B //嵌套一个命名空间B
{
int a =20;
int fun()
{
cout<<"a = "<<a<<endl;
}
}
}
int main(int argc, char *argv[])
{
cout<<a<<endl;
fun();
return 0;
}
函数模板可以重载,只要它们的形参表不同即可。例如,下面两个模板可以同时存在:/font>
template<class T1, class T2>
void print(T1 arg1, T2 arg2)
{
cout<<arg1<<" "<<arg2<<endl;
}
template<class T>
void print(T arg1, T arg2)
{
cout<< arg1<< " "<< arg2<< endl;
}
其实举个简单的例子反而更利于新手理解本质。
函数模板:
#include <iostream>
using namespace std;
template <typename T1>
void Swap(T1& a, T1& b)
{
T1 t = a;
a = b;
b = t;
}
int main()
{
int a = 2;
int b = 3;
cout <<"a = " << a << "; b = " << b <<endl;
Swap(a,b);
cout <<"a = " << a << "; b = " << b <<endl;
float c = 0.02;
float d = 0.03;
cout <<"c = " << c << "; d = " << d <<endl;
Swap(c,d);
cout <<"c = " << c << "; d = " << d <<endl;
return 0;
}
类模板:
#include <iostream>
using namespace std;
template <typename T>
class Op{
public:
T peocess(T v)
{
return v * v;
}
};
int main()
{
Op<int> opInt;
Op<double> opDouble;
cout << "5 * 5 = " << opInt.peocess(5) <<endl;
cout << "0.5 * 0.5 = " << opDouble.peocess(0.5) <<endl;
}