C++基础知识
Hello World
从hello wrld开始
需要导入iostream这个头文件
c++中打印使用cout关键字。不过使用的时候需要加上命名空间std。
每次都加std太麻烦,可以在外面声明一个全局的using namespace std
命名空间相当于java中的包,主要为了防止重名
1 | #include <stdio.h> |
自定义命名空间
1 | namespace NSP_A { |
C中没有布尔类型,C++中有了布尔类型,使用bool修饰。bool类型在内存中占一个字节。
C++中也有跟java一样的三目运算,不过C++中的三目运算运算完之后可以直接赋值,java中不可以。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17int main() {
bool success = true;
if (success) {
cout << "成功" << endl;
cout << sizeof(bool) << endl;
}
else {
cout << "失败" << endl;
}
//三目运算,最后可以直接复制
int a = 10, b = 20;
((a > b) ? a : b) = 30;
cout << b << endl;
system("pause");
}
C++中的引用。
使用&符号修饰。
1 | int main() { |
上面的代码中,b是a的引用。相当于a的一个别名。它跟指针不同,指针是指向变量的地址,引用只是变量的一个别名。
引用和指针的比较:
- 引用能干的事情,指针都能干,不过引用写起来别指针方便。引用可以直接操作变量,而指针需要在前面加个*
- 引用必须要有值不能为空。比如我们向一个函数传递一个引用参数,如果为空的话,编译期间就会报错,而如果是传递指针,指针可以为空,编译期间不报错,运行时就会报错了。这样来看使用引用更好。
1 | //指针值交换 |
引用主要用到的地方
作为函数的参数或者返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21struct Teacher{
char* name;
int age;
};
//引用作为参数
void myprint(Teacher &t){
cout << t.name << "," << t.age << endl;
t.age = 21;
}
//指针作为参数
void myprint2(Teacher *t){
//(*t).name
}
void main(){
Teacher t;
t.name = "Jason";
t.age = 20;
myprint(t);
myprint2(&t);
system("pause");
}拿指针的引用来代替二级指针
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21struct Teacher {
char* name;
int age;
};
//使用二级指针
void getTeacher(Teacher **p) {
Teacher *tmp = (Teacher*)malloc(sizeof(Teacher));
tmp->age = 20;
*p = tmp;
}
//使用引用
void getTeacher(Teacher* &p) {
p = (Teacher*)malloc(sizeof(Teacher));
p->age = 20;
}
void main() {
Teacher *t = NULL;
getTeacher(&t);
system("pause");
}
指针常量和常量指针
指针常量:就是不改变地址,但是可以改变它所指向的内容
常量指针:就是指向一个常量的指针,这个常量不可以被修改
1 | void main() { |
类
1 | class teacher { |
除了构造函数,析构函数,C++中还有拷贝构造函数。
1 | class Teacher{ |
上面代码中的拷贝构造函数就是默认的拷贝构造函数,不写它也会存在。
默认的拷贝构造函数拷贝的是指。这样有时候会有问题,比如下面,将t2赋值给t2,他俩都指向同一块内存区域,当t1调用了它的析构函数释放了内存之后,t2在释放内存就会出错。这种称为浅拷贝1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class Teacher{
private:
char *name;
int age;
public:
Teacher(char *name, int age){
this->name = (char*)malloc(100);
strcpy(this->name,name);
this->age = age;
cout << "有参构造函数" << endl;
}
~Teacher(){
cout << "析构" << endl;
//释放内存
free(this->name);
}
void myprint(){
cout << name << "," << age << endl;
}
};
void func(){
Teacher t1("rose", 20);
Teacher t2 = t1;
t2.myprint();
}
void main(){
func();
system("pause");
}
为了避免上面的问题,我们可以复写默认的拷贝构造函数,不拷贝值而是重新申请内存拷贝内容。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class Teacher{
private:
char *name;
int age;
public:
Teacher(char *name, int age){
int len = strlen(name);
this->name = (char*)malloc(len+1);
strcpy(this->name, name);
this->age = age;
cout << "有参构造函数" << endl;
}
~Teacher(){
cout << "析构" << endl;
//释放内存
free(this->name);
}
//深拷贝
Teacher(const Teacher &obj){
//复制name属性
int len = strlen(obj.name);
this->name = (char*)malloc(len+1);
strcpy(this->name,obj.name);
this->age = obj.age;
}
void myprint(){
cout << name << "," << age << endl;
}
};
void func(){
Teacher t1("rose", 20);
Teacher t2 = t1;
t2.myprint();
}
void main(){
func();
system("pause");
}
C++可以和C混编,所以C++中也可以使用结构体,虽然跟C中一样也叫struct,不过对C中的struct做了一些扩展。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20struct teacher {
private:
int age;
public:
void setAge(int age) {
this->age = age;
}
int getAge() {
return age;
}
};
int main() {
{
struct teacher t;
t.setAge(20);
cout << t.getAge() << endl;
}
system("pause");
}
struct默认为public,class默认为private。
class可以用来声明模板,struct不可以。
前面只是简单的了解了类,C++中写一个类,一般先写一个头文件,在里面声明要写的类,然后写一个C++文件来实现这个类中的方法。最后在main中使用。
头文件:student.h1
2
3
4
5
6
7
8
9
10
11
12
13//保证只会被引用一次,防止重复引用
#pragma once;
class student {
public:
int age;
char* name;
public:
void setAge(int age);
int getAge();
void setName(char* name);
char* getName();
};
然后写student.cpp1
2
3
4
5
6
7
8
9
10
11
12
13
14#include "student.h"
void student::setAge(int age) {
this->age = age;
}
int student::getAge() {
return this->age;
}
void student::setName(char* name) {
this->name = name;
}
char* student::getName() {
return this->name;
}
最后main中调用1
2
3
4
5
6
7
8
9
10int main() {
student s;
s.age = 20;
char name[] = "cha";
s.setName(name);
cout << s.getName() << endl;
system("pause");
}
构造函数的属性初始化
1 | class Teacher { |
怎么给Student中的成员变量Teacher中的name属性赋值呢?经过尝试是不能从构造方法中直接赋值的编译不通过,我们可以通过在构造函数后面加上一个冒号从后面赋值比如: t1(t1_n), t2(t2_n)
上面的代码,Student类中有Teacher类这个成员变量,那么他们的构造函数和析构函数式什么时候调用的呢?运行之后结果如下1
2
3
4
5
6
7Teacher有参构造函数
Teacher有参构造函数
Student有参构造函数
15,cha,cha
Student析构函数
Teacher析构函数
Teacher析构函数
可以看到,创建的时候,内部成员先调用构造函数,然后自身在调用。销毁的时候,自身先调用析构函数,内部成员在调用析构函数。
C++动态内存分配
- c++中通过new关键字分配内存,通过delete关键字释放内存
- c中通过malloc关键字申请内存,通过free关键字释放内存。
c++中也可以通过malloc和free来管理内存,跟第一种的区别是,通过new和delete管理,会执行构造函数和析构函数,而通过malloc和free来管理内存,不会执行构造函数和析构函数。
数组释放的时候,需要使用delete[]。
1 | class Teacher { |
C++中的静态属性和方法
静态类型的属性不能再main函数中初始化,应该在全局初始化
访问的时候,可以通过类名直接访问,也可以通过类对象访问1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40class Teacher {
public:
char* name;
static int total;
public:
Teacher(char* name) {
this->name = name;
cout << "Teacher有参构造函数" << endl;
}
~Teacher() {
cout << "Teacher析构函数" << endl;
}
void setName(char* name) {
this->name = name;
}
char* getName() {
return this->name;
}
//静态函数
static void count() {
total++;
cout << total << endl;
}
};
//静态属性初始化赋值
int Teacher::total = 9;
void main() {
Teacher::total++;
cout << Teacher::total << endl;
//直接通过类名访问
Teacher::count();
//也可以通过对象名访问
Teacher t1((char*)"yuehang");
t1.count();
system("pause");
}
C++中类的大小
C/C++ 内存分区:栈、堆、全局(静态、全局)、常量区(字符串)、程序代码区
1 | class A{ |
上面的代码中打印的结果都是12。因为静态变量和函数都是共享的,不包含在类的里面。
既然函数是共享的,那怎么知道是谁调用了它呢,调用的时候传入this,就可以区别开了。this是当前对象的指针。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43class Teacher{
private:
char* name;
int age;
public:
Teacher(char* name,int age){
this->name = name;
this->age = age;
cout << "Teacher有参构造函数" << endl;
}
~Teacher(){
cout << "Teacher析构函数" << endl;
}
//常函数,修饰的是this
//既不能改变指针的值,又不能改变指针指向的内容
//const Teacher* const this
void myprint() const{
printf("%#x\n",this);
//改变属性的值是不行的
//this->name = "yuehang";
//改变this指针的值也是不行的
//this = (Teacher*)0x00009;
cout << this->name << "," << this->age << endl;
}
void myprint2(){
cout << this->name << "," << this->age << endl;
}
};
void main(){
Teacher t1((char*)"jack",20);
const Teacher t2((char*)"rose", 18);
//常量对象只能调用常量函数,不能调用非常量函数
//t2.myprint2();
//常函数,当前对象不能被修改,防止数据成员被非法访问
printf("%#x\n", &t1);
t1.myprint();
printf("%#x\n", &t2);
t2.myprint();
system("pause");
}
上面的代码中 ,使用const来修饰一个函数,这个函数就是一个常量函数。如果一个类中有常量函数,那么这个类的对象不能被修改。可以防止类中的属性被非法访问。
友元函数和友元类
友元函数:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32class A {
//友元函数
friend void modify_i(A *p, int a);
private:
int i;
public:
A(int i) {
this->i = i;
}
void modify_i(int a) {
this->i = a;
}
void myprint() {
cout << i << endl;
}
};
//友元函数的实现,在友元函数中可以访问私有的属性
void modify_i(A *p, int a) {
p->i = a;
}
void main() {
A* a = new A(10);
a->myprint();
//modify_i(a, 20);
//a->modify_i(20);
a->myprint();
system("pause");
}
正常情况下,A中的属性i是私有的,不能被外部修改,如果非得要修改的话,可以通过友元函数。使用friend关键字修饰,比如上面定义一个友元函数modify_i。通过这个函数就可以修改私有属性i了。
友元函数,我们可以在类的内部实现,也可以在外部实现,如上面的代码。
友元类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class A {
//友元类
friend class B;
private:
int i;
public:
A(int i) {
this->i = i;
}
void myprint() {
cout << i << endl;
}
};
class B {
public:
//B这个友元类可以访问A类的任何成员
void accessAny() {
a.i = 30;
}
private:
A a;
};
友元类也是通过friend关键字修饰。在类A中声明友元类B,那么B中就可以访问和修改A中的私有变量了。
C++中的运算符重载
1 | class Point { |
上面的代码中,执行p1+p2让结果p3中的数值是p1和p2中的数值之和。这时候就用到运算符的重载。使用operator关键字来修饰,上面例子中修饰了加号,我们同样可以修饰减号,乘号,除号等。
跟前面的友元函数一样,我们可以把重载函数写在类的里面,也可以写在外面,如上面代码中,取其一即可。
前面运算符重载的代码中,重载的属性x和y都是public(公有的),假如是私有的属性,外面不能访问这时候可以通过友元函数来完成运算符的重载比如:friend Point operator+(Point &p1, Point &p2);
继承多态
1 | class Man { |
- C++中继承使用冒号来修饰,跟java不一样,C++中可以多继承。比如下面代码,Student继承自人和动物。
- 子类向构造方法传参,java中可以直接使用super关键字,c++中没有,想要传参需要在子类构造方法后面传比如
: Man((char*)"jack",18)
- 父类的构造函数先执行,子类的析构函数先执行。
- 如果子类中有跟父类同名的方法,比如上面代码中的sayHai方法,使用子类对象调用的时候调用的是子类的sayHai,如果想调用父类的此函数,需要指定父类显示调用如
s.Man::sayHai();
- 上面代码可以看到,继承的时候父类前面都加了访问修饰符。一般情况下都使用public,默认是private
基类 | 继承方式 | 子类 |
---|---|---|
public | public继承 | public |
public | protected继承 | protected |
public | private继承 | private |
protected | public继承 | protected |
protected | protected继承 | protected |
protected | private继承 | private |
private | public继承 | 子类无权访问 |
private | protected继承 | 子类无权访问 |
private | private继承 | 子类无权访问 |
虚继承和虚函数
虚继承: 不同路径继承来的同名成员只有一份拷贝。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25class A{
public:
char* name;
};
class A1 : virtual public A{
};
class A2 : virtual public A{
};
class B : public A1, public A2{
};
void main(){
B b;
b.name = "大海";
//如果没有 virtual关键字只能 指定父类显示调用
//b.A1::name = "大海";
//b.A2::name = "大海";
system("pause");
}
上面的代码中A1,A2都继承A,B继承了A1和A2,A中有个name属性,那么如果我们调用b.name就会报错,因为系统不知道你是调用A1的还是A2的。这时候只能显示的调用指定调用哪一个的比如b.A1::name = "大海"
。
正常情况下我们希望直接使用b.name就能调用,这时候继承的时候使用virtual关键字来修饰A,就可以了,这是虚继承。
虚函数: 相当于java中的多态。使用多态可以增加程序的可扩展性,使用virtual关键字修饰函数
动态多态:程序运行过程中,觉得哪一个函数被调用(重写)
静态多态:重载
能够使用多态的条件:
- 使用了继承
- 父类的引用指向子类的实现
- 子类重写了父类的函数
例子:
定义一个动物基类1
2
3
4
5
6
7#pragma once
class Animal {
public:
virtual void eat();
virtual void drink();
};
1 | #include "Animal.h" |
定义一个兔子继承动物1
2
3
4
5
6
7#pragma once
#include "Animal.h"
class Rabbit : public Animal {
virtual void eat();
virtual void drink();
};
1 | #include "Rabbit.h" |
使用:业务函数中需要传入一个基类Animal,使用的时候,Animal的子类和和它自己都能传入,使用虚函数之后,就可以调用自己的相关方法了。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21#include <stdio.h>
#include <iostream>
#include "Animal.h"
#include "Rabbit.h"
using namespace std;
//业务函数
void bizAnimal(Animal &a) {
a.eat();
a.drink();
}
void main() {
Animal a;
bizAnimal(a);
Rabbit r;
bizAnimal(r);
system("pause");
}
纯虚函数
纯虚函数相当于java中的抽象类
1 | class Shape{ |
- 当一个类有一个纯虚函数的时候,这个类就是一个抽象类。
- 抽象类不能实例化对象
- 子类继承抽象类,必须要实现纯虚函数,如果没有实现,那么子类也是抽象类。
- 接口跟抽象类的写法一模一样,只是用到的时候,逻辑上叫它接口。
模板函数和模板类
模板函数相当于java中的泛型,泛型主要解决业务逻辑一样,数据类型不一样的问题
比如下面代码
1 | void swap(int& a,int& b){ |
这两个函数都是实现的一样的功能就是交换,只是传入的数据不一样,这时候就可以抽取一个模板。使用template关键字来修饰,让类型自动推导。
1 | template <typename T> |
模板类:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38//模板类
template<class T>
class A{
public:
A(T a){
this->a = a;
}
protected:
T a;
};
//普通类继承模板类
class B : public A<int>{
public:
B(int a,int b) : A<int>(a){
this->b = b;
}
private:
int b;
};
//模板类继承模板类
template <class T>
class C : public A<T>{
public:
C(T c, T a) : A<T>(a){
this->c = c;
}
protected:
T c;
};
void main(){
//实例化模板类对象
//List<String> list;
A<int> a(6);
system("pause");
}
异常
C++ 异常处理,根据抛出的异常数据类型,进入到相应的catch块中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18void main() {
try {
int age = 300;
if (age > 200) {
throw "10.0";
}
}
catch (int a) {
cout << "int异常" << endl;
}
catch (const char *b) {
cout << b << endl;
}
catch (...) {
cout << "未知异常" << endl;
}
system("pause");
}
上面代码中我们throw什么类型的异常,就会进入对应的下面catch中。catch (...)
代表捕获为止异常。
我们也可以自定义自己的异常类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23class MyException {
};
void mydiv(int a, int b) {
if (b == 0) {
throw MyException();
//不要抛出异常指针
//throw new MyException;
}
}
void main() {
try {
mydiv(8, 0);
}
catch (MyException& e2) {
cout << "MyException引用" << endl;
}
catch (MyException* e1) {
cout << "MyException指针" << endl;
delete e1;
}
system("pause");
}
上面代码中,定义自己的异常类MyException
,抛出异常的时候可以直接throw MyException()
然后捕获。也可以使用/throw new MyException
不过通过new的这种方式抛出的是异常的指针,捕获到之后还要释放内存,所以不要使用这种方式太麻烦。
C++ 标准的异常:
C++ 提供了一系列标准的异常,这些类都是 exception 类派生而来的 中,我们可以在程序中使用这些标准的异常。
我们也可以通过集成exception来自定义一个异常。
1 | #include <stdexcept> |
类型的转换
C++提供了4中类型转换操作符
- const_cast:修改类型的const或volatile属性
- static_cast:静态类型转换,如int转换成char,指针与void之间互转。如:float转成void、Bean转成void、函数指针转成void*等;子类指针/引用与 父类指针/引用 转换。
- dynamic_cast:动态类型转换,比如子类和父类之间多态类型的转换
- reinterpret_cast:对指针、引用进行原始转换
使用格式:
TYPE B = static_cast(TYPE)(a)
类型强制转换的时候,其实我们可以直接使用(TYPE)这种形式转换比如int i; double j = (int)i
,C++吧转换类型细化,可以让意图更加明显,可读性更高。
1 | void* func(int type){ |
假如我们想要修改一个使用const修饰的常量,正常情况下无法修改,只能通过指针间接的去修改,但是别人阅读起这个代码来就比较费劲了。而使用const_cast,意图明显就更加容易理解。1
2
3
4
5
6
7
8
9
10
11
12
13
14void func(const char c[]){
//通过指针间接赋值,其他人并不知道,这次转型是为了去常量
//char* c_p = (char*)c;
//c_p[1] = 'X';
//提高了可读性
char* c_p = const_cast<char*>(c);
c_p[1] = 'Y';
cout << c << endl;
}
void main(){
char c[] = "hello";
func(c);
system("pause");
}
子类和父类之间多态类型的转换使用dynamic_cast来提高可读性,防止不可预见的错误。
1 | class Person{ |
上的代码中,一个函数中需要传入一个Person对象,方法内部使用原始转换方式强制转换成Boy对象。加入我们传入一个Girl对象进来,其实是转型失败的。使用原始方式即使转型失败我们也察觉不到。使用dynamic_cast,如果转型失败会返回一个NULL值。
使用reinterpret_cast来转型函数指针1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28using namespace std;
void func1(){
cout << "func1" << endl;
}
char* func2(){
cout << "func2" << endl;
return "abc";
}
//定义函数指针类型
typedef void(*f_p)();
void main(){
//函数指针数组
f_p f_array[6];
//赋值
f_array[0] = func1;
//C方式
//f_array[1] = (f_p)(func2);
//C++方式
f_array[1] = reinterpret_cast<f_p>(func2);
f_array[1]();
system("pause");
}
C++中的io操作
1 | #include <iostream> |
1 | #include <iostream> |
C++对象的持久化
1 | class Person |
C++标准模板库
(stl standard template library )
1 | #include <string> |
序列式容器:元素的排列顺序与元素本身无关,由添加的顺序决定。
序列容器一般有:List,vertor,queue,dequeue,stack,priority queue,
关联式容器:如set,map
1 | #include <vector> |