C++
一、Hello World
二、数据类型
2.1 基本类型
数据类型 | 占用空间 |
---|---|
short | 2字节 |
int | 4字节 |
long | windows 4字节 linux 4字节(32位)8字节(64位) |
long long | 8字节 |
float | 4字节 |
double | 8字节 |
char | 1字节 |
bool | 1字节 |
bool | 1字节 |
sizeof(数据类型or变量)获取数据类型占用内存空间大小
cout<<"语句"<<endl;
cin>>变量>>endl;
goto FLAG;
FLAG:xxxx
system("命令");
2.1 数组
数组是内存中一块连续空间,数组名称是数组首地址
元素数据类型 数组名称[] = {元素1,元素2....}
int arr[] = {1,2,3};
// 输出元素个数
cout>>sizeof(arr)/sizeof(arr[0])<<endl;
// 二维数组第二个参数不能省略,2行3列
int arr[][3]={1,2,3,4,5,6}
// 行数
cout<<sizeof(arr)/sizeof(arr[0])<<endl;
// 列数
cout<<sizeof(arr[0])/sizeof(arr[0][0])<<endl;
2.2 指针
指针在32位操作系统占4字节空间,在64位操作系统占8字节空间
// 空指针,指向编号为0地址,没有访问权限
int * p = NULL;
// 野指针,指向非法内存空间(不是当前程序分配内存空间)
int * p = (int *)0x1100;
// 常量指针,指针修饰的变量可以改变指向,但是无法改变指针的值
const int * p = &a;
// 指针常量,指针指向不可以改,但是指针的值可以改
int * const p = &a;
// 将a地址复制一份传给p
void fun(int* p){
}
fun(&a);
// 在C语言中有void * 指针(万能指针)而C++中取消了
int a = 10;
void * p = &a;
// 万能指针可以引用任何地址,但使用时需要转换到实际类型
*(*int)p = 10;
// 指针数组,数组每一个元素都是一个指针
int * arr[3] = {&a,&b,&c};
// 可以通关指针修改常量值
const int a = 10;
int * p = &a;
*p=20;
指针变量只能进行加减操作,加减单位是存储地址中存储的实际类型占用的内存空间值
函数不要返回局部变量地址,因为函数调用完成会销毁,局部变量第二次调用会销毁
2.3 函数
函数定义在使用之后,则函数一定要先声明再调用,声明可以多次,定义只能一次。 声明函数可以给默认参数。 函数声明时可以不写形参,只写类型,就是占位参数。 函数在类外部也支持重载,同一作用域,函数名称相同,参数类型。 函数重载可以通过const区别
返回值类型 函数名 (参数类列表){
函数体语句;....
return 返回值或表达式;
}
// 防止头文件重复包含
函数分文件编写:.h文件写函数声明和引用其它类库 .cpp文件写函数定义和声明函数的.h文件
2.4 结构体
函数参数是结构体,建议用引用传递,如果是值传递则会复制一份相同的结构体,如果是引用传递则只会复制一份地址值;防止在函数中修改结构体值,可以用常量结构体指针
struct 结构体名{
成员列表...
} [变量名];
// 结构体指针通过->符号访问结构体变量的值
struct studet{
string name;
int age;
} *p;
cout<<p->name<<endl;
// 为结构体赋值
struct student = {"cai",12};
// 防止在函数中修改结构体变量.并且引用传递不会复制结构体副本,只会复制地址值,减少内存占用
void func(const student * stu){
}
2.5 共用体
不能在定义共用体初始化,不能用共用体变量作为函数参数,可以用共用体指针作为参数,也可以定义共用体数组
union 共用体类型{
成员变量...
}[变量名];
2.6 枚举
对枚举元素做常量处理,不能进行赋值,默认有序0,1,2,3....,可以用来比较
enum week {sun,mon};
C++面向对象
一、内存分区模型
-
代码区:存放函数二进制代码,由操作系统管理,是共享只读的
-
全局区:存放全局变量和静态变量(static修饰)以及全局常量,程序结束后由操作系统自动释放(const局部常量和局部变量放在一起)
-
栈区:编译器自动释放,存储函数参数值、局部变量等
-
堆区:程序员分配和释放,若不释放,则在程序结束后自动释放
通过 new 可以在堆上分配内存,通过指针引用(指针变量在栈上分配),通过 delete 删除堆空间内存
通过typeid()返回类型
int a[] = new int[10];
delete[] a;
// 创建引用变量
int a = 10;
// 相当于a的别名,引用必须初始化,且初始化后不可改变
int &b = a;
int c = 20;
// 并不是改变应用是改变b和a的值
b = a;
// 引用传递使用的是原值,而不是原值的拷贝
void func(int &a){
}
func(a);
// 返回值是引用可以作为左值
int& func(){
// static 修饰变量放在常量区,防止函数返回后清除
static int a = 20;
return a;
}
func() = 30;
// 引用本质上是指针常量,一旦初始化不能改变
// 等价于 int const * ref = &a;
int &ref = a;
// 等价于 *ref = 10;
ref = 10;
// 引用值必须是一个合法内存空间,不能是值常量
const int &a = 10;
// 在堆区分配内存
int a = new int(10);
二、面向对象
public:类内外都可访问 protected:类外不可访问,子类可以访问父类 private:类外不可访问,子类不可访问(默认)
编译器提供 构造函数 和 析构函数 默认是空,创建对象系统自动调用 构造函数,对象销毁钱自动调用 析构函数
C++返回值如果是对象值,则会返回对象的浅拷贝(与原对象变量地址不同),值传递参数,返回值参数,使用对象初始化另外一个对象会默认调用拷贝构造函数 C++为每个空对象分配一个字节的空间,为了区分空对象占用独一无二的内存空间,如果不是空,则会占用成员变量的空间大小 this隐含在每一个非静态函数 c++给一个类添加四个默认函数:默认构造函数、默认析构函数、默认拷贝构造函数、赋值运算符重载;当用另外一个同类对象初始化对象时调用的是拷贝构造函数;初始化之后通过=
赋值对象调用的是 赋值运算符重载
// 构造函数,可以有参数可以重载
类名(){}
// 析构函数,无参数不可重载
~类名(){}
//拷贝构造函数
类名()}{}
浅拷贝带来的问题是在析构函数中对堆区内存重复释放
class Person{
public:
// 为a、b指定初始值
Person(int a,int b):m_a(a),m_b(b){
m_a = new int(a);
m_b = new int(b);
}
Person(int a){
this->m_a=a;
}
int m_a;
int m_b;
~Person(){
delete m_a;
delete m_b;
}
static int m_c;
static void func(){
// 防止空指针
if(NULL==this){
return;
}
// 属性前面默认加上this
m_c = 10;
cout<<"静态方法调用"<<endl;
}
void say(){
cout<<"静态方法调用"<<endl;
}
}
int main(){
Person p(1,2);
// 访问静态函数
p.func();
Person::func();
return 0;
// 8字节,static成员变量放在静态区
cout<<sizeof(p)<<endl;
Person *person = NULL;
person->say();
}
const修饰成员函数叫 常函数 常函数不能修改成员属性值,成员属性加上mutable
后可以被常函数修改,const修饰的对象叫做 常对象 只能调用 常函数
class Person{
public:
mutable int m_A;
int m_B;
// this指针指向的值不可修改,即m_B不可修改
void showPerson () const{
this->m_A=10;
}
void say () {
cout<<1<<endl;
}
}
void main(){
const Person p;
// 常对象可以调用常函数,但是不能调用普通函数,因为普通函数会改变属性值
p.showPerson();
}
友元:让一个函数或者类访问另一个函数的私有成员
-
全局函数做友元
class Student{
// 友元函数是全局函数,不被类内的任何修饰符限制(即使有)
// 类内声明,类外定义
friend void say*(Student *stu);
// 类内声明定义,全局函数
friend void info(){
cout<<"info"<<endl;
}
private:
int m_Age;
}
void say(Student *stu){
cout<<stu->m_Age<<endl;
}
void main(){
Student stu;
stu.say(&stu);
}
-
类做友元
// 声明类
-
成员函数做友元
运算符重载
class Person{
public:
Person operator+(Person &p){
a = new int(10);
}
int m_Num;
int* a;
// 前置操作符重载
Person& operator++(){
m_Num++;
return *this
}
// 后置操作符重载,(占位参数表示是后置运算符)
Person operator++(int){
Person temp = *this;
m_Num++;
return temp;
}
};
// 第一个参数是调用的对象
// 全局重载
ostream operator<<(ostream &cout,Person p){
cout<<p.name<<endl;
return cout;
}
// 深拷贝
Person& operator=(Person &p){
if(a!=NULL){
delete a;
a = NULL;
}
a = new int(*(p.a));
return *this;
}
C++支持多继承,但一般不使用;父类中的所有属性都会继承到子类,只是私有成员不可见
// 公共继承 父类属性全部继承到子类,但私有不可见
class Son:public Base;
// 公共继承 父类属性全部继承到子类,公共属性变成保护,但私有不可见
class Son:protected Base;
// 公共继承 父类属性全部继承到子类,公共和保护属性变成私有属性,但私有不可见
class Son:private Base;
// 访问父类属性
class Base{
public:
int m_A;
Base(){
m_A = 10;
}
static m_B;
static void say(){}
};
// 类外初始化静态变量
int Base::m_B = 20
class Son:public Base{
};
int main(){
Son s;
Son::Base::m_B;
Son::Base::say();
s.Base::m_A;
return 0;
}
// 多继承
class A:public B,public C;
// 当多继承的父类出现重复定义属性,必须通过::区分不同域对象属性
// 利用虚继承,解决父类重复属性;虚基类保存一个
class A{
public:
int a;
};
class B:virtual public A{};
class C:virtual public A{};
// 此时D两个属性a(从B、C继承)都只是一个指针(vbprt),指向了虚基类表,表中记录了虚基类属性(A中的a属性)偏移量
class D:public B,public C{};
多态分为两类
-
静态多态:函数重载、运算符重载,函数重载;地址早绑定,编译阶段确定
-
动态多态:父类引用指向子类对象;地址晚绑定,运行阶段确定
-
有继承关系
-
重写父类虚函数(virtual)
-
父类指针或者引用 指向子类对象
-
class Animal{
public:
// 地址早绑定
void speak(){
cout<<"动物在说话"<<endl;
}
// 地址晚绑定
virtual void speak(){
cout<<"动物在说话"<<endl;
}
};
class Cat:public Animal{
public:
void speak(){
cout<<"猫在说话"<<endl;
}
};
void doSpeak(Animal &animal){
animal.speak();
};
int main(){
Cat cat;
// 地址早绑定,编译时就确定会调用父类方法
doSpeak(cat);
return 0;
}
动态多态原理:虚函数记录一个vfprt(虚函数指针)指针,指向虚函数表,虚函数表中记录函数实现入口地址;子类重写虚函数后覆盖子类的虚函数表中记录的入口地址(父类虚函数指针没变,还是指向以前的虚函数表地址)
父类中的虚函数可以不实现,直接赋值0就是 纯虚函数。即:virtual 返回值类型 函数名(参数列表)=0;
当类中有一个纯虚函数,该类被称为 抽象类;抽象类无法实例化对象,子类如果不实现纯虚函数也会成为 抽象类
C++高级
c++中用模板实现泛型编程,分为 函数模板 和 类模板
函数模板
函数模板中类型参数不能有默认参数
普通函数 和 函数模板 重名调用规则:
-
如果两者都可以调用,则优先调用普通函数
-
通过空参数列表强制调用函数模板,例:int<>(1)
-
函数模板可以重载
-
如果 函数模板 更匹配则调用 函数模板
template<typename T>
void func(T t){
};
// 提供具体类型实现规则
template<> void func(Person &p){
};
// 显示指定类型,可以发生隐式类型转换(向上转型)
func<int>(1);
// 自动类型推导,如果使用自动类型推导,则不会发生隐式类型转换(向上转型)
func(1)
类模板
类模板 没有自动类型推导;类模板 在类型参数列表中可以定义默认参数;普通成员函数在创建对象时候就就创建,类模板 中成员函数在调用时创建,动态绑定;类模板 中的类型参数必须在创建对象时指定
类模板 中定义友元函数
仿函数
类中重载()运算符,将对象像函数一样调用,可以实现闭包;重载()并且返回值是bool类型,则将该类称为谓词