C++ readme
避免头文件重复定义,
#pragma once
//或者头文件有一个自动生成的宏
__头文件的宏_H
extern C的用法:
#ifdef __cplusplus
extern "C" {
int sum(int v1, int v2);
int delta(int v1, int v2);
int divide(int v1, int v2);
}
#endif // __cplusplus
内联函数: inline function ,关键词: inline ,使用的时候编译器会直接将代码粘贴
什么时候使用:
- 调用频繁
- 函数代码少
关键词:const
int age = 10;
int height = 30;
int score = 99;
//p1 指向可以改变, *p1不能改变
const int *p1 = &age; //(可以 p1 = &score; 不能 *p1 = score );
//p2 指向可以改变, *p2不行
int const *p2 = &age; //(同上)
//p3 指向不能改变 *p3 可以改变
int * const p3 = &age; //(可以 *p3 = score; 不能 p3 = &score );
// p4是常量,*p4也是常量
const int * const p4 = &age;
// p5是常量,*p5也是常量
int const * const p5 = &age;
引用:类似于指针,但不能更改,定义时必须初始化,
引用存在的价值: 比指针更安全、函数返回值可以被赋值
//注意,这里是&v1 所以可以直接传a和b,如果是指针 int* 则需要 swap(&a,&b)
void swap(int& v1, int& v2) {
int tmp = v1;
v1 = v2;
v2 = tmp;
}
swap(a, b);
cout << "a = " << a << ", b = " << b << endl;
常引用:引用被const修饰,这样就无法通过引用修改数据。
const引用的特点:
1、可以指向临时数据(常量、表达式、函数返回值等)
2、可以指向不同类型的数据
3、作为函数参数时
✓ 可以接受const和非const实参(非const引用,只能接受非const实参)
✓ 可以跟非const引用构成重载
4、 当常引用指向了不同类型的数据时,会产生临时变量,即引用指向的并不是初始化时的那个变量
数组的引用:常见的两种写法
int array[] = {10,20,30}
int (&ref1)[3] = array;
int * const &ref2 = array;
类,对象的内存布局
struct Person{
int m_id;
int m_age;
int m_height;
void display() {//可以用this,但不能this.m_age,因为this是指针
cout << "m_id is " << this->m_id << endl;
cout << "m_age is" << m_age << endl;
cout << "m_height is" << m_height << endl;
}
};
Person person; //0x00E69B60
person.m_id = 10; //0x00E69B60
person.m_age = 20; //0x00E69B64
person.m_height = 30; //0x00E69B68
Person* p = (Person*)&person.m_age; //0x00E69B64
p->m_id = 40; //不偏移地址直接赋值 0x00E69B64 = 40 即person.m_age = 40
p->m_age = 50; //往后偏移4个地址 0x00E69B68 = 50; person.m_height = 50
//指针访问对象成员的本质
person.display(); //m_id is 10 m_age is40 m_height is50
p->display(); //m_id is 40 m_age is50 m_height is-858993460
封装: 成员变量私有化,提供公共的getter和setter给外界去访问成员变量
struct Student{
private:
int age;
public:
void setAge(int age) {
this->age = age;
}
int getAge() {
return this->age;
}
};
内存布局:
代码段: 用于存放代码
数据段: 用于存放全局变量等
栈空间:没调用一个函数就会给它分配一段连续的栈空间,等待函数调用完毕后会自动回收,自动分配和回收
堆空间: 需要主动去申请和释放 , 为了能够自由控制内存的生命周期、大小,会经常使用堆空间的内存
堆空间的申请与释放
// malloc free
int *p = (int *) malloc(4);
*p = 10;
free(p);
//new delete
char *p = new int;
*p = 10;
delete p;
//new[] delete[]
char *p = new char[4];
delete[] p;
int *p1 = new int; //空间未初始化
int *p2 = new int();//空间初始化为0
int *p3 = new int(5);//空间初始化为5
int *p4 = new int[3];//空间未被初始化
int *p5 = new int[3]();//3个元素都被初始化为0
int *p6 = new int[3]{};//3个元素都被初始化为0
int *p7 = new int[3]{5};//首元素初始化为5,其他元素初始化为0
memset函数是将较大的数据结构(比如对象、数组等)内存清零的比较快的方法
Person person;
memset(&person, 0, sizeof(person));
Person persons[] = { { 1, 20, 180 }, { 2, 25, 165}, { 3, 27, 170 } };
memset(persons, 0, sizeof(persons));
内存空间:
//全局区
Person g_person;
int main() {
//栈空间
Person person;
//堆空间,没有初始化成员变量
Person* p1 = new Person;
//堆空间,初始化成员变量为0
Person* p2 = new Person;
return 0;
}
构造函数,析构函数
对象初始化还可以在构造函数中添加:
Person(){
memset(this, 0, sizeof(Person));
}
注意 :
通过malloc分配的对象free的时候不会调用析构函数
构造函数、析构函数要声明为public,才能被外界正常使用
struct Car{
int m_price;
};
struct Person{
int m_age;
Car m_car;
Car* my_car;
};
my_car 需要自己申请空间,自己回收.
m_car 对象创建就有,与对象内存空间关联
类的声明和实现:
h文件
#pragma once
namespace Sean { //自定义命名空间
class Person{
private:
int m_age;
public:
void setAge(int age);
int getAge();
};
}
cpp文件
#include "Person.h"
namespace Sean{
void Person::setAge(int age) {
m_age = age;
}
int Person::getAge() {
return m_age;
}
}
命名空间 namespace 避免命名冲突
Sean::Person* p = new Sean::Person();
using namespace Sean; //优先使用此命名空间
//这样就可以直接像下面这样写
Person* p = new Person();
初始化列表:
//注意,这个需要在.h里面生命
Person::Person(int age, int height) :m_age(age), m_height(height) {
}
Person::Person(int age, int height) :m_height(height), m_age(m_height) {
}
m_age 和 m_height分别是多少
Person p(20,180);
m_age未知,m_height 为180
构造函数互相调用:
Person::Person(){
new(this) Person(10, 20);
}
虚函数:关键字: virtual
只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类中可以省略virtual关键字 )
虚表:虚函数的实现原理是虚表,虚表里面存储着最终调用函数的地址,这个虚表也叫虚函数表
class Animal {
public:
int m_age;
//纯虚函数 类似OC的协议接口,只有声明,未实现
virtual void speak() = 0;
virtual void run() = 0;
};
class Cat : public Animal{
public:
int m_life;
void speak() {
//调用父类的成员函数
Animal::speak();
cout << "Cat::speak()" << endl;
}
void run() {
cout << "Cat::run()" << endl;
}
};
所有的Cat对象(不管在全局区、栈、堆)共用同一份虚表
Animal* cat = new Cat();
cat->m_age = 20;
cat->speak();
cat->run();
在内存中的形式:
如果存在父类指针指向子类对象的情况,应该将析构函数声明为虚函数(虚析构函数)
delete父类指针时,才会调用子类的析构函数,保证析构的完整性
多继承
初始化方法:
class Student{
int m_score;
public:
void eat(){}
Student(int score) : m_score(score) {
};
};
class Worker{
int m_salary;
public:
void eat(){}
Worker(int m_salary) : m_salary(m_salary) {
};
};
class Undergraduate: public Student, public Worker{
public:
Undergraduate(int score, int m_salary) : Student(score), Worker(m_salary) {
};
};
多继承-虚函数
如果子类继承的多个父类都有虚函数,那么子类对象就会产生对应的多张虚表
同名函数,同名变量
Undergraduate ug;
ug.Student::eat();
ug.Worker::eat();
ug.Student::m_age;
ug.Worker::m_age;