前言
为了准备秋招及春招,现阶段的任务是将所学过的知识进行复习,并串联起来。
首当其冲的就是编程语言的复习,C++自己平时写的也比较多,但仍有很多语法细节不太记得了,这里浅记一些平常容易忽略的要点。
C++基础入门
一维数组数组名
一维数组名称的用途:
- 可以统计整个数组在内存中的长度(单位:字节B)
- 可以获取数组在内存中的首地址
已知一个数组的数组名,求数组元素的个数,如下:
1 2 3 4
| int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; cout << "整个数组所占内存空间为: " << sizeof(arr) << endl; cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl; cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;
|
可以通过数组名获取到数组首地址:
1 2 3
| cout << "数组首地址为: " << (int)arr << endl; cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl; cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;
|
这里首地址即为第一个元素的地址
指针
指针所占内存空间
所有指针类型在32位操作系统下是4个字节
1 2 3 4
| cout << sizeof(int *) << endl; cout << sizeof(char *) << endl; cout << sizeof(float *) << endl; cout << sizeof(double *) << endl;
|
空指针和野指针
空指针:指针变量指向内存中编号为0的空间
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
野指针:指针变量指向非法的内存空间
1 2 3 4
| int * p = (int *)0x1100;
cout << *p << endl;
|
两者共同点:都不能访问
const修饰指针
const修饰指针有三种情况:
-
const修饰指针 — 常量指针 (const int * p)
const修饰的是指针,指针指向可以改,指针指向的值不可以更改
-
const修饰常量 — 指针常量 (int * const p)
const修饰的是常量,指针指向不可以改,指针指向的值可以更改
-
const即修饰指针,又修饰常量 (const int * const p)
const既修饰指针又修饰常量,两者均不可修改
指针和数组
利用指针访问数组中的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
cout << "第一个元素: " << arr[0] << endl; cout << "指针访问第一个元素: " << *p << endl; cout << "指针访问第二个元素: " << *(p+1) << endl;
for (int i = 0; i < 10; i++) { cout << *p << endl; p++; }
|
指针和函数
利用指针作函数参数,可以修改实参的值
1 2 3 4 5 6 7 8
| void swap2(int * p1, int *p2) { int temp = *p1; *p1 = *p2; *p2 = temp; } swap(&a, &b);
|
如果是数组名作为函数参数的话,两种写法效果相同,因为数组名就代表了首元素的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void bubbleSort(int * arr, int len) { for (int i = 0; i < len - 1; i++) { for (int j = 0; j < len - 1 - i; j++) { if (arr[j] > arr[j + 1]) { int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp; } } } }
|
结构体
结构体指针
通过指针访问结构体中的成员
利用操作符 ->
可以通过结构体指针访问结构体属性
1 2 3 4
| student stu = { "张三",18,100}; student * p = &stu; p->score = 80; cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
|
函数参数
结构体作函数参数时,如果不想修改主函数中的数据,用值传递,反之用地址传递
1 2 3 4
| void printStudent2(student *stu);
printStudent2(&stu);
|
C++核心编程
内存4大分区
C++程序在执行时,将内存大方向划分为4个区域:
- 代码区:存放函数体的二进制代码,由操作系统进行管理的。特点共享、只读。
- 全局区:存放全局变量、静态变量、常量,该区域的数据在程序结束后由操作系统释放
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
- 堆区:由程序员分配和释放内存(new),若程序员不释放(delete),程序结束时由操作系统回收
new操作符
C++中利用new
操作符在堆区开辟数据
利用new创建的数据,会返回该数据对应的类型的指针
开辟数组
1 2
| int* arr = new int[10]; delete[] arr;
|
引用&(指针常量)
作用:给变量起别名,本质上还是原来那个变量
- 引用必须初始化
- 引用在初始化后,不可以改变(易错)
1 2 3 4 5 6 7 8 9
| int a = 10; int b = 20;
int& c = a; c = b;
cout << "a = " << a << endl; cout << "b = " << b << endl; cout << "c = " << c << endl;
|
由于c是a的别名,c在被重新赋值后,a也会随之改变
这个特点也印证了,引用本质上是一个指针常量,即指针指向的值可以修改,但是指针的指向不允许修改。
引用作函数参数
可以代替指针,实现函数内修改实参的操作
通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
1 2 3 4 5 6
| void mySwap03(int& a, int& b) { int temp = a; a = b; b = temp; } mySwap03(a, b);
|
函数重载
函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型、个数、顺序至少要有一个不同
注:函数返回值不可以作为函数重载条件
类与对象
构造/析构函数写法
类的定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Person { public: Person() { cout << "无参构造函数!" << endl; mAge = 0; } Person(int age) { cout << "有参构造函数!" << endl; mAge = age; } Person(const Person& p) { cout << "拷贝构造函数!" << endl; mAge = p.mAge; } ~Person() { cout << "析构函数!" << endl; } public: int mAge; };
|
利用类创建对象
1 2 3
| Person man1; Person man2(100); Person newman(man);
|
深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的重复释放内存空间的问题
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
| class Person { public: Person() { cout << "无参构造函数!" << endl; } Person(int age, int height) { cout << "有参构造函数!" << endl; m_age = age; m_height = new int(height); } Person(const Person& p) { cout << "拷贝构造函数!" << endl; m_age = p.m_age; m_height = new int(*p.m_height);
}
~Person() { cout << "析构函数!" << endl; if (m_height != NULL) { delete m_height; } } public: int m_age; int* m_height; };
|
列表初始化
一种简化语法的操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| class Person { public:
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {} private: int m_A; int m_B; int m_C; };
|
静态成员变量
即可以通过对象名访问,也可以通过类名访问(类名::静态成员变量名
)
静态成员变量特点:
- 在编译阶段分配内存
- 类内声明,类外初始化
- 所有对象共享同一份数据
定义与初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13
| class Person { public: static int m_A; private: static int m_B; }; int Person::m_A = 10; int Person::m_B = 10;
Person p1; p1.m_A; Person::m_A;
|
this指针(重要)
this指针在Java中也存在,不过用法上还是略有区别
this指针指向被调用的成员函数所属的对象
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Person { public: Person(int age) { this->age = age; }
Person& PersonAddPerson(Person p) { this->age += p.age; return *this; }
int age; };
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
|
友元
友元分为三种:
第一种最简单,直接声明前加上friend
关键字,放在类的最前头即可
1
| friend void goodGay(Building * building);
|
第二种需要考虑到类定义的顺序
被友元访问的那个类,需要事先声明,顺序如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class B;
class A { public: ... private: B *b; };
class B { friend class A; };
|
第三种成员函数作友元,与类友元类似,不过只有类中特定的一个成员函数可以访问。总体形式一样,区别在于声明语句
1
| friend void goodGay::visit();
|
运算符重载
不能重载的运算符有5种,分别是.
、->
、sizeof
、?:
、::
加号
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
| class Person { public: Person() {}; Person(int a, int b) { this->m_A = a; this->m_B = b; } Person operator+(const Person& p) { Person temp; temp.m_A = this->m_A + p.m_A; temp.m_B = this->m_B + p.m_B; return temp; }
public: int m_A; int m_B; };
Person operator+(const Person& p2, int val) { Person temp; temp.m_A = p2.m_A + val; temp.m_B = p2.m_B + val; return temp; }
|
使用方法
1 2 3 4
| Person p1(10, 10); Person p2(20, 20); Person p3 = p2 + p1; Person p4 = p3 + 10;
|
左移
只能在全局函数中实现
1 2 3 4 5 6
|
ostream& operator<<(ostream& out, Person& p) { out << "a:" << p.m_A << " b:" << p.m_B; return out; }
|
并且要作为友元,放在原类中
1
| friend ostream& operator<<(ostream& out, Person& p);
|
继承
基本语法
class A : public B;
A 类称为子类 或 派生类
B 类称为父类 或 基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| class Base1 { public: int m_A; protected: int m_B; private: int m_C; };
class Son1 :public Base1 { public: void func() { m_A; m_B; } };
|
成员函数变量同名
- 子类对象可以直接访问到子类中同名成员
- 子类对象加作用域可以访问到父类同名成员
- 当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数
1 2 3 4
| s.m_A; s.Base::m_A; s.func(); s.Base::func();
|
多态
父类指针或引用指向子类对象
- 静态多态: 函数重载和运算符重载属于静态多态,复用函数名——编译阶段确定函数地址
- 动态多态: 派生类和虚函数实现运行时多态——运行阶段确定函数地址
父类定义一个虚函数,子类通过重写虚函数实现多态
1 2 3 4
| virtual void speak() { ... }
|
计算器
创建一个计算器父类与加法子类,并重写虚函数
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
|
class AbstractCalculator { public :
virtual int getResult() { return 0; }
int m_Num1; int m_Num2; };
class AddCalculator :public AbstractCalculator { public: int getResult() { return m_Num1 + m_Num2; } };
|
使用方法:
1 2 3 4 5 6
| AbstractCalculator *abc = new AddCalculator; abc->m_Num1 = 10; abc->m_Num2 = 10; cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl; delete abc;
|
纯虚函数与抽象类
这个点也是比较陌生的点,平常很少用到不涉及,且和Java有一定程度上的区别
考虑到父类一般是抽象类,所编写的纯虚函数可能并不存在对应的实现,因此就要把它定义为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为==抽象类==
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
- 类中只要有一个纯虚函数就称为抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Base { public: virtual void func() = 0; };
class Son :public Base { public: void func() { cout << "func调用" << endl; }; };
|
虚析构与纯虚析构
存在的问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
解决方式:将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
- 可以解决父类指针释放子类对象(作用),提醒子类必须重写自己的析构函数
- 都需要有具体的函数实现
虚析构和纯虚析构区别:
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
| class Animal { public:
Animal() { cout << "Animal 构造函数调用!" << endl; } virtual void Speak() = 0;
virtual ~Animal() = 0; };
Animal::~Animal() { cout << "Animal 纯虚析构函数调用!" << endl; }
class Cat : public Animal { public: Cat(string name) { cout << "Cat构造函数调用!" << endl; m_Name = new string(name); } void Speak() { cout << *m_Name << "小猫在说话!" << endl; } ~Cat() { cout << "Cat析构函数调用!" << endl; if (this->m_Name != NULL) { delete m_Name; m_Name = NULL; } } public: string* m_Name; };
|
未完待续……
参考资料
- 黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难
- 《C++ Primer》