一、基础进阶部分
1、基本常识及概念扩充:
1)、“cout <<” 、"cin >>"与“输入”、“输出”
#include <iostream>
using namespace std;
int main()
{
int i = 0;
cout << "Hello World!" << endl;
cin >> i;
return 0;
}
1、“cout <<”:
"cout"实际上是C++定义的对象名,称为“输出流对象”。而"<<"则是插入运算符,配合“cout”使用,实现将"<<"后的数据插入到输出队列中(输出流),进而将其输出到显示屏上。
2、“cin >>”:
"cin"同理"cout",是C++定义的“输入流对象”。">>"则称作“提取运算符”,配合"cin"使用,实现从输入设备中(如键盘)提取输入数据送入输入流,进而获取数据。
3、“endl”
2、引用(Reference)概念以及使用:
1)、引用概念
#include <iostream>
using namespace std;
int main()
{
int n = 4;
int &r = n;
cout << "n = " << n << endl;
//输出“n = 4”
cout << "r = " << r << endl;
//输出“r = 4”
double a = 4, b = 5;
double & r1 = a;
double & r2 = r1;// r2也引用 a
r2 = 10;
cout << a << endl;// 输出 10
r1 = b;// r1并没有引用b
cout << a << endl;
return 0;
}
这段代码,定义了一个整型变量“n”,并初始化。同时,又定义了一个整型引用变量“r”,并将其初始化为引用变量“n”。
引用就相当于被用作初始化变量的“别名”。引用的出现,扩充了函数传递数据的功能。从指针变量传地址,上升到引用传变量。
注意:引用使用
1、定义引用时一定要将其初始化成引用某个变量。
2、初始化后,它就一直引用该变量,不会再引用别的变量了。
3、引用只能引用变量,不能引用常量和表达式。
2)、将变量名作为实参
#include <iostream>
using namespace std;
void swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main(int argc, char *argv[])
{
int i = 3, j = 5;
swap(i, j);
cout << "i = " << i << endl;
cout << "j = " << j << endl;
return 0;
}
利用引用作为函数参数,以扩充函数传递数据的功能。同时,减少了对指针以及相关指针运算符号的使用。
3)、定义常引用
int a = 0;
const int &r = a;
定义引用时,在最前面加上“const”关键字,即为常引用。此时,“r”的数据类型为“const int &”。
int a = 1;
const int &r = a;
r = 10;//错误
a = 11;//正确
不能通过常引用去修改其所引用的变量的值。
注意:const T & 和T & 是不同的类型!!!:
T & 类型的引用或T类型的变量可以用来初始化 const T & 类型的引用。
const T 类型的常变量和const T & 类型的引用则 不能用来初始化T &类型的引用,除非进行强制类型 转换。
引用原本的变量,而不去开辟一个新地址,很大程度上减少了内存的开销,是十分经济的做法。
3、“const”关键字的用法:
1)、定义常量
const int MAX_VAL = 23;
const String str = "Hello World!";
2)、定义常指针
int n, m;
const int *p = &n;
*p = 5; //Error
n = 4; //PASS
p = &m; //PASS,常量指针的指向可以改变
不能通过常量指针修改其所指向的内容。
const int *p1;
int *p2;
p1 = p2; //PASS
p2 = p1; //Error
p2 = (int *) p1; //PASS,强制类型转换
不能把常量指针赋值给非常量指针,反过来非常量指针可以给常量指针赋值.
void myPrintf(const char *p)
{
strcpy(p, "this"); //Error
printf("%s", p); //PASS
}
函数参数为常量指针时,可以避免函数内部改变参数指针所指位置的内容。
3)、定义常引用
int n = 0;
const int &r = n;
r = 5; //Error
n = 4; //PASS
不能通过常引用修改其引用的变量。
4、动态内存分配:
1)、对变量的分配
int *p = new int;
*p = 5;
动态分配出一片大小为 sizeof(int) 字节的内存空间,并将该内存空间的起始地址赋值给p。再做 *p = 5 进行整型变量的赋值。
2)、对数组的分配
int *p = new int[10];
p[0] = 1; //数组中单一元素赋值
p[20] = 3; //Build可以通过,Running时会发生数组越界的情况
常用于创建初始长度未知的数组,输入数组长度,然后通过动态分配内存建立一个刚好的数组。
3)、通过 delete 释放 new 动态分配出的内存
int *p = new int;
*p = 5;
delete p; //释放指针p所指向的new出来的整型变量内存空间
int *pn = new int[10];
pn[0] = 1;
delete[] pn; //释放指针pn所指向的new出来的整型数组内存空间
切记,delete 数组时,一定要加上 [ ] 方括号才可以。否则只会释放 pn[0]。
5、函数:
1)、内联函数
inline int Max(int a, int b)
{
if (a > b)
{
return a;
}
else
{
return b;
}
}
为了减少简短的函数在被调用时的时间开销,在C++中,引入了 “内联函数” 机制。编译器在处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
2)、函数重载
int Max(double f1, double f2){} // 1
int Max(int n1, int n2){} // 2
int Max(int n1, int n2, int n3){} // 3
Max(3.4, 2.5); //调用1
Max(2, 4); //调用2
Max(1, 2, 3); //调用3
Max(3, 2.4); //Error,存在二义性
一个或者多个函数,在名字相同的前提下,函数的参数个数或者参数类型不同,叫做函数的重载。函数重载的出现,使得函数的命名变得简单。编译器会根据调用语句中的实参个数和类型判断应该调用哪个函数。
3)、函数的缺省参数
void fun(int x1, int x2 = 2, int x3 = 3){}
fun(10); //x1 = 10, x2 = 2, x3 = 3
fun(10, 8); //x1 = 10, x2 = 8, x3 = 3
fun(10, , 8); //Error,只能最右边的连续若干个参数缺省
C++中,定义函数的时候,可以让函数“最右边的连续若干个参数”有缺省值,那么调用函数的时候,若对应位置不写参数,参数就是缺省值。其目的在于提高程序的可扩充性。其意义在于,倘若一个写好的函数需要添加新的参数,而原先调用该函数的语句又未必会使用新增加的参数,那么为了避免对原先函数调用语句的修改,就可以使用缺省参数。
void add(int a = 0, int b = 0);
void add(int a, int b)
{
A = a;
B = b;
}
对于函数声明与定义分开时,应当将缺省参数写入声明中。
注意:
1、参数缺省,采取的是从右向左的顺序,且必须连续
2、调用实参对形参的初始化必须时从左向右
3、使用缺省参数时,避免出现重载时的二义性
二、类与对象
1、类与对象
1)、定义
#include <iostream>
using namespace std;
class Student
{
public:
void addName();
void addAge();
void addSex();
void addStudentNumber();
void getName();
void getAge();
void getSex();
void getStudentNumber();
private:
char name[10];
int age;
char sex;
int studentnumber;
};
int main()
{
Student stu;
return 0;
}
类的定义不过多赘述,Java有些东西可以照搬。
注意:
1、C++对类的定义,千万不要缺失了最后的分号。原因是class就是原来的struct,或者说C++对于类定义当成了一个固定语句。
2、new 在C++中,可不是用来定义对象的。用于开辟内存的。所以C++定义新的对象就是上面的那种形式。
2)、对象的内存分配
和结构变量一样,对象所占用的内存空间大小,不单纯是所有成员变量之和的大小,具体涉及到内存的对齐问题。所以,想知道占了多少内存,还是用 sizeof 去求吧。
每个对象拥有自己的内存空间,改变一个对象的一个私有成员变量,不会波及到另一个对象。
3)、对象间的运算
同结构变量一样,对象之间可以用“=”进行赋值,但是不能使用“==”、“!=”、“>”、“<”、“>=”、“<=”做比较运算,除非这些运算符经过了“重载”。
2、使用类的成员变量和成员函数
class CRectangle
{
public:
int w, h;
int Area()
{
return w * h;
}
int Perimeter()
{
return 2 * (w + h);
}
void Init(int w_, int h_)
{
w = w_;
h = h_;
}
};
1)、对象名.成员名
CRectangle r1, r2;
r1.w = 5;
r2.Init(5,4);
2)、指针->成员名
CRectangle r1, r2;
CRectangle *p1 = &r1;
CRectangle *p2 = &r2;
p1->w = 5;
p2->Init(5,4);
3)、引用名.成员名
CRectangle r1;
CRectangle &rr = r2;
rr.w = 5;
rr.Init(5,4);
3、类成员的可访问范围
在类的定义中,用下列访问范围关键字来说明类成员的可被访问的范围:
private:私有成员,只能在成员函数内访问
public:共有成员,可以在该对象存在的任何地方通过对象访问
protected:保护成员,对于本类抽象出的对象,访问权限跟私有成员一样
1、以上三种关键字的出现次数与先后顺序都没有限制
2、class定义类,如果没有访问范围关键字修饰,默认作为 private成员处理
3、struct定义类,则默认作为 public成员处理
class Test
{
private:
//私有属性的成员变量和函数
public:
//共有属性的成员变量和函数
protected:
//保护属性的成员变量和函数
};
4、成员函数的重载及参数缺省
跟普通函数,类的成员函数也可以重载和缺省参数
#include <iostream>
using namespace std;
class Location
{
private:
int a, b;
public:
//缺省参数的声明
void init(int A = 0, int B = 0);
//重载
void valueA(int val)
{
A = val;
}
int valueA()
{
return a;
}
};
//缺省参数函数的定义
void Location::init(int A, int B)
{
a = A;
b = B;
}
int main()
{
Location A,B;
A.init(5);
A.valueA(5);
cout << A.valueA();
return 0;
}
注意:要避免使用缺省参数时出现的重载二义性
class Test
{
private:
int a = 1;
public:
void valueA(int A = 0)//删除“ = 0”,正常调用 int valueA()
{
a = A;
}
int valueA()
{
return a;
}
};
int main()
{
Test t;
t.valueA();
}
Error: call of overloaded 'valueA()' is ambiguous
G++在编译时,就会给出上面的错误,说明使用了参数缺省后,无法判断调用哪个 valueA
5、构造函数
1)、构造函数基本概念
- 构造函数是成员函数的一种,名字与类的名字相同,可以有参数,没有返回值
- 作用是对对象进行初始化,例如给成员变量赋初值
- 如果定义类时没有写构造函数,则编译器会生成一个默认的无参数的构造函数,但是其不会做任何操作。如果定义了构造函数,编译器则不会生成无参数的构造函数
- 对象生成时,构造函数被自动调用,且只调用一次。生成后,不会再调用构造函数。
- 一个类可以有多个构造函数(函数的重载)
#include <iostream>
using namespace std;
class Test
{
private:
int number = 0;
int value = 0;
int count = 0;
int time = 0;
public:
Test(int N);
Test(int N, int V);
Test(Test &a, Test &b);
void printTest();
};
Test::Test(int N)
{
number = N;
}
Test::Test(int N, int V)
{
number = 2 * N;
value = 2 * V;
}
Test::Test(Test &a, Test &b)
{
number = a.number + b.number;
value = a.value + b.value;
count = a.count + b.count;
time = a.time + b.time;
}
void Test::printTest()
{
cout << number << " " << value << " " << count << " " << time << endl;
}
int main()
{
Test T(1);
Test E(2, 3);
Test S(T, E);
cout << "T: " << endl;
T.printTest();
cout << "E: " << endl;
E.printTest();
cout << "S: " << endl;
S.printTest();
getchar();
getchar();
return 0;
}
构造函数最好是“public”,“private”不能直接用来初始化对象
#include <iostream>
using namespace std;
class CSample
{
private:
CSample(){ }
};
int main()
{
CSample Obj; //Error,唯一构造函数是private
return 0;
}
2)、构造函数在数组中的使用
#include <iostream>
using namespace std;
class CSample
{
private:
int x;
public:
CSample()
{
cout << "Constructor 1 Called" << endl;
}
CSample(int n)
{
x = n;
cout << "Constructor 2 Called" << endl;
}
};
int main()
{
CSample array1[2];
cout << "step1"<<endl;
//Constructor 1 Called
//Constructor 1 Called
//step1
CSample array2[2] = {4,5};
cout << "step2"<<endl;
//Constructor 2 Called
//Constructor 2 Called
//step2
CSample array3[2] = {3};
cout << "step3"<<endl;
//Constructor 2 Called
//Constructor 1 Called
//step3
CSample *array4 = new CSample[2];
//Constructor 1 Called
//Constructor 1 Called
delete []array4;
return 0;
}
使用类定义对象数组时,实际上是同时定义了数组上限个数的该类的对象,所以每个定义才会出现调用两次构造函数的情况
class Test
{
public:
Test(int n) {} //(1)
Test(int n, int m) {} //(2)
Test() {} //(3)
};
int main()
{
Test array1[3] = {1, Test(1,2)};
// 三个元素分别用(1),(2),(3)初始化
Test array2[3] = {Test(2,3), Test(1,2), 1};
// 三个元素分别用(2),(2),(1)初始化
Test *pArray[3] = {new Test(4), new Test(1,2)};
//两个元素分别用(1),(2) 初始化
return 0;
}
6、复制构造函数
1)、概念
在C++中,有时需要用到多个完全相同的对象,如果使用直接定义多个对象的方法,比较麻烦。同时呢,有时又需要将对象某个瞬间的状态值保留下来。于是,复制构造函数孕育而生。
#include <iostream>
using namespace std;
class Test
{
private:
int test = 0;
int value = 0;
public:
Test(int t, int v)
{
test = t;
value = v;
}
Test(const Test &T) //将该复制构造函数注释掉,结果依旧不变
{
test = T.test;
value = T.value;
}
void testPrint()
{
cout << "test: " << test << endl;
cout << "value: " << value << endl;
}
};
int main()
{
Test T(2, 3);
Test S(T);
S.testPrint();
return 0;
}
结果输出的是,“test: 2”,“value:3”。表明,对象S通过调用复制构造函数,变成了对象T的复制品。
注意:
1、复制构造函数,只有一个参数,即——对同类的对象的引用
2、格式可以是 class::class(class &c) 或者 class::class(const class &c),二者选一。但是,后者的const前缀,可以让其将常量对象作为实参
3、如果没有定义复制构造函数,编译器会生成默认的复制构造函数,也会完成复制功能
4、如果定义了自己的复制构造函数,编译器不会再生成默认的复制构造函数
2)、复制构造函数起作用的三种情况
- (1)、当用一个已经初始化的对象去初始化同类另一个新定义的对象时
Test T(2, 3);
Test S(T);
Test E = T;
- (2)、如果某函数有一个参数是类A的对象,那么,在这个函数被调用时,类A 的复制构造函数将被调用
void Func(A a2){}
int main()
{
A a1
Func(a1);
return 0;
}
- (3)、如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用
class A
{
public:
int v;
A(int n){v = n;}
A(const A &a)
{
v = a.v;
cout << "Copy constructor called" << endl;
}
};
A Func()
{
A b(4);
return b;
}
int main()
{
cout << Func().v << endl;
return 0;
}
// 输出结果:
// Copy constructor called
// 4
注意:对象之间的相互赋值,并不会导致复制构造函数被调用
调用复制构造函数时,由于有额外开销,可以考虑使用" class & "对象的引用类型作为构造函数
7、类型转换构造函数
- 定义转换构造函数的目的是为了实现类型的自动转换
- 只有一个参数,且参数不是本类对象的常引用,而是一个单独的基本变量形参
- 需要时,编译器会自动调用该函数,将形参放入一个临时对象中
class Test
{
public:
int a = 0, b = 0;
Test(){}
Test(int A)
{
a = A;
b = -1;
}
}
int main()
{
Test T1;
T1 = 9;
cout << "T1: " << "a = " << T1.a << "b = " << T1.b << endl;
}
这段程序,最后会输出“a = 9 b = -1”
类型转换构造函数,其实也就是某一个重载的构造函数。只要有一个含有一个基本类型参数的构造函数,其所充当的,一个是被重载的构造函数,再一个就是类型转换构造函数
8、析构函数
- 名字与类名相同,在前面加‘~’, 没有参数和返回值,一 个类最多只能有一个析构函数
- 析构函数对象消亡时即自动被调用。可以定义析构函数来在 对象消亡前做善后工作,比如释放分配的空间等
- 如果定义类时没写析构函数,则编译器生成一个缺省的析构函数,但是,这个析构函数什么也不做,函数本身是空的
- 如果定义了析构函数,则编译器不生成缺省析构函数
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
T = 10;
}
private:
int T = 0;
};
int main()
{
Test test;
return 0;
}
上面这个例子,没有自己定义析构函数,同时,类定义的对象在消失后,其占用的空间被自动回收,析构函数没有任何作用
#include <iostream>
using namespace std;
struct Node
{
int data;
struct Node *next;
};
class LinkQueue {
private:
Node *front, *rear;
public:
LinkQueue()
{
Node *p = new Node;
p->next = NULL;
front = rear = p;
}
~LinkQueue()
{
Node *p = front;
while(front != NULL)
{
front = front->next;
delete p;
p = front;
}
}
};
int main()
{
LinkQueue linkqueue;
return 0;
}
虽然主函数只有两句,被定义的对象,却调用了一次构造函数和一次析构函数。初始对象执行构造函数,动态分配了一个内存结点;主函数进行至“return 0”,标志该对象完成了使命,因此会再度执行析构函数,进行善后回收处理,将动态分配的内存做回收释放处理
析构函数的写与不写是相对的,倘若类的成员变量均为基本类型变量,可以完全由编译器生成默认的缺省析构函数,进而什么都不做;如果出现动态内存分配这样的情景,倘若什么都不做,就会一直占用系统内存,进而产生问题,此时就需要手动定义析构函数,做相应的善后工作