最近开始准备春招,写个博客就当复习了
1.获取数组中的元素个数
int arr[] = {1,2,3,4,5,6,7,8,9}; cout<<"该数组共有:"<<sizeof(arr)/sizeof(arr[0])<<endl; cout<<"数组的首地址:"<<arr<<endl; cout<<"数组元素的首地址:"<<&arr[0]<<endl;
2.数组逆序
int arr[] = {1,2,3,6,5,8}; int len = sizeof(arr)/sizeof(arr[0]),temp;//获取该数组的长度 // cout<<len; int arr2[len]; for(int i = len-1;i>=0;i--) { arr2[(len-i)-1]=arr[i]; // cout<<len-i-1<<" "; } for(int i = 0;i<len;i++){ cout<<arr2[i]<<endl; }
3.指针和引用
//在x86操作系统下,指针占4字节,在64位操作系统下指针占8字节 int a = 10; int *p = &a; //指针一定要初始化(有指向),不然变成野指针 *p = 1000; //p指向a的地址,*p代表该地址的内容,修改*p相当于修改a cout<<"此时的a值被修改为:"<<a<<endl; cout<<"sizeof(int *)"<<sizeof(p)<<endl;
//指针常量和常量指针 int a = 10; int b = 20; // 指针常量,只能修改内容,指向不可修改 int * const p = &a; *p = 100; // 常量指针,只能修改指向,不能修改内容 const int * p2 = &a; p2 = &b; // 混合方式,都不可以修改 const int * const p3 = &a;
记忆:指针常量 const修饰的是p,p指向a的地址,由于const则指向不可修改
常量指针const修饰的是*p,*p是内容,则内容不能修改
//指针和数组 int arr[] = {1,2,3,4,5,6,7,8,9,10}; int *p = arr; cout<<"数组的第一个元素:"<<*p<<endl; p++; //有一种写法是P+4,因为int占4个字节,但有潜在隐患,即不同处理机数据类型可能不一 cout<<"数组的第二个元素:"<<*p<<endl;
指针还有一个用法是解决修改形参问题,还有一种方式就是使用引用
//用指针的方式
int a = 10; int b = 20; swap(&a,&b); cout<<a<<endl; cout<<b<<endl;
//用引用的方式
但是需要注意:
int& ref = test01(); cout<<"ref:"<<ref<<endl; //输出6 test01() = 666;//使用引用做返回值,那么函数可以做左值修改 cout<<"ref:"<<ref<<endl; //输出666
右值引用:右值引用有一个重要的性质—只能绑定到一个将要销毁的对象。 因此,我们可以自由地将一个右值引用的资源“移动”到另一个对象中。
4.函数重载
需满足三个条件:
void fun(int &a){ //调用的是变量 } void fun(const int &a){ //调用的是常量 } void func(int a){ }
5.类的访问权限
1.public 类内类外皆可访问 2.protected 类内可以,类外不可以 子可以访问父中的内容 3.private 类内可以,类外不可以 子不可以访问父中的内容 struct 默认是public class 默认是private
protected和private的主要区别在继承中体现的明显一点:
用protected继承时,派生类可以访问类内成员,而使用private继承时,派生类是访问不到类内成员的
6.类和类的各种构造函数
class Phone{ public: Phone(string name) :m_PName(name) { cout<<"phone"<<endl; } string m_PName; ~Phone(){ cout<<"Phone的析构"<<endl; } }; class Person{ ~Person(){ cout<<"Person的析构"<<endl; } Person(){ cout<<"person"<<endl; } string m_name; Phone m_phone; };
浅拷贝的危害:在析构函数执行时(新开辟的指针指向员有指针的相同空间),导致该堆区数据被释放两次,导致程序奔溃
7.static
1.全局静态变量
在全局变量前加static就变成静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
作用域:从定义开始处到文件结尾 初始化:未经初始化的话,自动初始化为0
如下,我们并未给num显示赋值,但结果为0
#include<iostream> using namespace std; static int num;//全局静态变量 int main(){ cout<<"num: "<<num<<endl; system("pause"); return 0; }
2.局部静态变量
同上也存放在全局数据区
作用域:局部作用域,但离开作用域后并未销毁,任驻留在内存中 初始化:未经初始化的话,自动初始化为0
如下,在func中声明一个局部静态变量,并未初始化,但两次调用中一次为0(自动初始化),第二次为1,代表该数据值得到了保留(即在全局数据区)
#include<iostream> using namespace std; void func(){ static int num;//声明一个局部静态变量 cout<<"num:"<<num<<endl; num++; } int main(){ func(); func(); system("pause"); return 0; }
3.静态函数
在函数返回类型前加static,就被定义为静态函数
函数默认情况下是extern的,但加static后在其他文件中便不可见,比如上面代码的func加一个static修饰,其他文件中便无法感知
4.类的静态成员
静态成员类是类的所有对象中共享的成员,而不是某个对象的成员。
对多个对象来说,静态数据成员只存储一份,供所有对象共用
如下程序中,我们对a进行赋值,只输出最后一个赋值的结果,这是因为a仅被存储了一份。而非静态成员则是由每个对象各自维护。
#include<iostream> using namespace std; class Static_exp{ public: static int m_a; int m_b; Static_exp(int a,int b){ this->m_a = a; this->m_b = b; } void get_a_b(){ cout<<this->m_a<<" "<<this->m_b<<endl; } }; int Static_exp::m_a=0;//注意一定要初始化 int main(){ Static_exp s1(10,10),s2(20,20),s3(30,30); s1.get_a_b(); s2.get_a_b(); s3.get_a_b(); system("pause"); return 0; }
5.类的静态函数
静态成员函数和静态成员变量一样,都属于类的静态成员,不是对象成员
因此,对静态成员的引用不需要用对象名
且1.所有对象共享同一个函数 2.静态成员函数只能访问静态成员变量
class Person{ public: //静态成员函数 static void func(){ m_A = 100; //静态成员函数可以访问静态成员变量 // m_B = 200; //错误!!不能访问非静态成员变量 } static int m_A;//静态成员变量 int m_B;//非静态成员变量 };
普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。
8.C++内存分区模型
C++内存模型可以分为:
1.代码区:二进制代码,由OS进行管理
2.全局区:进一步细分为静态存储区和常量存储区
静态存储区:主要存储全局静态变量,局部静态变量,全局变量,以及虚函数表
常量存储区:全局常量,函数指针,常量数组
3.栈区:由编译器自动分配释放,存放函数的参数值,局部变量等
4.堆区:由程序员手动分配和手动释放,程序员未释放在程序运行结束后由OS释放
做几个简单的验证
class MyClass { public: int m_c; static int m_d; }; int MyClass::m_d = 0; const int c_g_a = 10; int main() { int a = 10; int b = 10; static int c = 10; const int c_l_a = 20; cout << "局部变量地址:" << (int)&a << endl; cout << "局部变量地址:" << (int)&b << endl; cout << "全局变量地址:" << (int)&m_a << endl; cout << "全局变量地址:" << (int)&m_b << endl; MyClass mc; cout << "MyClass变量地址:" << (int)&mc << endl; cout << "MyClass中的m_c:" << (int)&mc.m_c << endl; cout << "MyClass中的m_d(静态成员属性):" << (int)&mc.m_d << endl; cout << "局部静态变量c:" << (int)&c << endl; cout << "const修饰的全局变量a:" << (int)&c_g_a << endl; cout << "const修饰的局部变量a:" << (int)&c_l_a << endl; system("pause"); return 0; }
红色区域(全局区)的地址和黄色区域(栈区)的地址明显有一定的差距,注意两个相邻全局的地址正好相差4个字节,正好对应int变量
栈区数据和堆区数据的释放时机不同,堆区的生命周期由程序员决定,而栈区是当该变量使用结束后便释放,如下
#include<iostream> using namespace std; int* func() { int a=10;//局部变量存放在栈区,栈区数据在执行完后自动释放 return &a; } int main() { int *p = func(); cout << *p << endl; cout << *p << endl; system("pause"); return 0; }
第一次可以正常输出是编译器做了保留,但也就保留一次,第二次就乱码了。
和上面堆区的数据做一个对比,我们将func()中的a开辟在堆区再打印便不会有任何问题
#include<iostream> using namespace std; int* func() { int *a= new int (666); return a; } int main() { int *p = func(); cout << *p << endl; cout << *p << endl; cout << *p << endl; system("pause"); return 0; }
关于虚函数,虚函数表的问题可以看这篇博客:C++虚函数表剖析 | Leo的博客 (leehao.me)
虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。
9.this指针
作用:1.解决名称冲突,2.返回对象本身用*this 本质:this指针的本质是指针常量,即指向不可修改
#include<iostream> using namespace std; class Person_this { public: Person_this(){} Person_this(int age) { this->age = age;//不用this的话,就成了命名冲突 }//1.当形参和成员变量同名时,用this指针来区别 Person_this& PersonAddAge(Person_this &p) { this->age += p.age; // this指向p2的指针,而*this指向的就是p2的本体 //返回对象本身 return *this; } //常函数 void show_person() const { //加上const修饰的是this指针本来为:Perosn* const this 变为 const Perosn* const this //this->age = 100;//报错
this->m_B = 200;//正确
} int age; mutable int m_B;//加上mutable关键字之后,变为特殊变量,即使在常函数中也可以修改 }; void test06() { const Person_this p;//此时声明的p为一个常对象,常对象是不能修改属性的--->mutable修饰的除外 // 且常对象只能调用常对象 //p.age = 10;//立马报错,const对象只能修改mutable修饰的 p.m_B = 100; p.show_person(); cout << "m_B:" << p.m_B << endl; Person_this p1(10); Person_this p2(10); cout << "age:" << p1.age << endl; // 链式编程思想 p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); cout << "p2:age:" << p2.age << endl; } int main() { test06(); system("pause"); return 0; }
返回引用 返回值
如上中的const修饰的常函数一般用于变量输出,防止在该函数中对变量进行修改
而返回*this即返回对象本身,再结合函数的引用类型可以实现链式编程(上左),即一直调用(如cout后可以追加任意多个<<),但是如果返回值类型不是引用,那么就变为了返回对象的值(上右)
10.友元
友元主要是用来解决访问不到私有类的问题,分为:
1.全局函数做友元 friend 函数名(); 2.类做友元 friend 类名; 3.成员函数做友元 friend 类名::函数名();
#include<iostream> #include<string> using namespace std; class Gay_friend { public: void visit_Person(); }; class Person { friend void visit();//让该全局函数做友元 friend class Gay_friend;//类做友元 friend void Gay_friend::visit_Person(); //成员函数做友元 public: void visit_sittingroom(string name); private: void visit_bedroom(string name); }; void Person::visit_sittingroom(string name) { cout << name << "正在访问客厅..." << endl; } void Person::visit_bedroom(string name) { cout<<name << "正在访问卧室..." << endl; } void Gay_friend::visit_Person(){ string name = "友元类"; Person p; p.visit_sittingroom(name); } void visit() {//全局函数做友元 string name = "全局函数"; Person p; p.visit_bedroom(name); } int main() { visit(); Gay_friend gf; gf.visit_Person(); system("pause"); return 0; }
11.运算符重载
主要解决自定义数据类型的运算操作
#include<iostream> #include<string> using namespace std; class MyInteger { friend int operator+(MyInteger &p1, MyInteger &p2); friend ostream& operator<<(ostream &cout, MyInteger myint); public: MyInteger(int num, int age) { m_num = num; m_age = new int(age); } MyInteger(int num) { m_num = num; } ~MyInteger() { if (m_age != NULL) {//若age在堆区开辟数据,则释放堆区内存 delete m_age;//此时若不做深拷贝则会重复释放同一内存而报错 m_age = NULL; } } // 重载关系运算符 bool operator==(MyInteger &p) { if (this->m_age == p.m_age && this->m_num == p.m_num) { return true; } return false; } //重载赋值运算符 MyInteger& operator=(MyInteger &p) { // 应该先判断是否有属性在堆区,有的话先释放干净 // 即p2=p1,其中的p2在堆区已经有内存了,我们先将他的原有内存释放,再为期开辟一个新的 if (m_age != NULL) { delete m_age; m_age = NULL; } // 深拷贝 m_age = new int(*p.m_age); // 返回对象自身 return *this;//这里返回对象本身,是为了实现链式规则 } int *m_age; private: int m_num; }; //重载加号(双目运算符的重载要在类外实现才行) int operator+(MyInteger &p1, MyInteger &p2) { return p1.m_num + p2.m_num; } // 将左移运算符重载 ostream& operator<<(ostream &cout, MyInteger myint) { cout << myint.m_num; return cout; } int main() { MyInteger p1(1, 18); MyInteger p2(1, 120); MyInteger p3(1, 1111); if (p1 == p2) { cout << "p1 p2等" << endl; } else { cout << "p1 p2不等" << endl; } cout << "p1+p2的值:" << p1 + p2 << endl; p3 = p2 =p1;//重载=号后可以运行 cout << "p1:" << *p1.m_age << endl; cout << "p2:" << *p2.m_age << endl; cout << "p3:" << *p3.m_age << endl; system("pause"); return 0; }
其中=号的重载是用来解决浅拷贝的,导致浅拷贝的原因:m_age是一个指针类型,开辟在堆区
假设:有p1和p2两个对象,此时执行p1=p2,则p1的m_age指向p2的m_age开辟的那块内存空间,这在赋值上确实没错,但在对象销毁时就出错了
p2执行析构,释放该空间,p1由于也指向这里,则又释放一次,此时程序就崩溃了
12.类型转化
1.static_cast:
static_cast 用于内置的数据类型、还用于有继承关系的指针或者引用
#include<iostream>
using namespace std;
class Animal{};
class Cat{};
class Dog:public Animal{};
void test01(){
int a=97;
char c = static_cast<char>(a);
cout<<c<<endl;
Animal *ani=NULL;
//Cat *cat = static_cast<Cat *>(ani);//没有继承关系无法转换
Dog *dog = static_cast<Dog *>(ani);//将父类指针转子类
Animal animal;
Animal &aniref = animal;
Dog& d = static_cast<Dog&>(aniref);//将父类引用转子类
}
int main(){
test01();
system("pause");
return 0;
}
2.reinterpret_cast
reinterpret_cast 强制类型转换无关指针类型,包括函数指针都可以进行转换
Animal *animal;
Cat *cat = reinterpret_cast<Cat*>(animal);
//animal 和 cat毫无关系也能转化,不像上面还需要继承
3.dynamic_cast:
dynamic_cast 转换具有继承关系的指针或者引用(用法同static_cast),在转换前会进行对象类型检查 和static_cast对比,dynamic_cast会做安全检查
比如子类指针转父类指针是安全的,但父类指针转子类指针就不安全了,主要是担心越界
4.const_cast:
const_cast 基础数据类型、指针、引用或者对象指针。作用、把const取消掉、
// 4.const_cast:增加或者消除const
int a = 10;
const int& b =a;
//b=20;//这里是修改不了的
int &c = const_cast<int &>(b);
c = 20;
cout<<a<<endl;
const int *p1=NULL;
int *p2 = const_cast<int *>(p1);//消除p1的const给p2
const int *p4 = const_cast<const int*>(p2);//为p3加上const给p3
13.内联函数
函数调用是需要额外开销的,假如有一些短小简单的函数被频繁调用,会大量消耗栈空间,此时可以将函数定义为内联函数。
需要注意的点:
inline void test02(int& a,int& b){
int temp = a;
a = b;
b = temp;
14.封装,继承,多态(C++面向对象三大特性)
1.封装:
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。
在C++中 struct和class唯一的区别就在于 默认的访问权限不同
struct默认是public,而class默认是private
2.继承:
无论是那种继承,基类的私有属性都不可以访问
class Father{ public: Father(){ cout<<"父类构造"<<endl; } ~Father(){ cout<<"父类析构"<<endl; } int m_a; protected: int m_b; private: int m_c; }; class Son:public Father{ public: Son(){ cout<<"子类构造"<<endl; } ~Son(){ cout<<"子类析构"<<endl; } };
现在问题来了?继承之后Son的内存模型是什么样的呢?
我们sizeof(Son)看一下,发现结果是12,这表明子类将父类中的属性全部继承下来了(无论你是public还是private),但是编译器做了隐藏
使用VS的开发者命令提示符 输入:cl /d1 reportSingleClassLayoutSon "exp.cpp" 可以看到son的内存模型是这样的
--菱形继承问题:一个子类多继承的俩个父类又同时继承于同一个父类,如图
如何解决:使用虚继承
class Animal{ public: virtual void speak(){ cout<<"animal..."<<endl; } int m_age; }; class Sheep:virtual public Animal{ }; class Tuo:virtual public Animal{ }; class SheepTuo:public Sheep,public Tuo{ }; void test17(){ SheepTuo st; st.Sheep::m_age = 18; st.Tuo::m_age = 200; // 这种写法的问题是重复继承了,这时应该用虚继承 cout<<st.Sheep::m_age<<endl; cout<<st.Tuo::m_age<<endl; // 此时内存中仅存在一份m_age数据,两个父类通过vbptr指向子类中的该属性 cout<<st.m_age<<endl; // 此时所有输出都为200,因为实际上大家都是一个变量,即后面的赋值会覆盖前面的赋值 }
同上使用开发者命令提示符查看SheepTuo,可以看到它分别继承了一个来自Sheep的vbptr和一个来自Tuo的vbptr,即实际上继承的是指针
vbptr: virtual base pointer 虚基类指针
vftable: virtual function table 虚函数表
3.多态:
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
多态的条件:1.有继承关系,2.子类重写父类中的虚函数
1.编译时多态-->通过函数重载 -->早绑定 -->编译阶段确定函数地址
2.运行时多态-->通过虚函数 -->晚绑定 -->运行阶段确定函数地址
比如:
class Base_19{ public: virtual void func()=0;//声明为纯虚函数 // 1.无法实例化对象 // 2.抽象类的子类,必须重写父类中的纯虚函数,不然也无法实例化对象 }; class Son_19 : public Base_19{ public: virtual void func(){ cout<<"func()"<<endl; } }; void test19(){ // 用多态技术调用 父类指针或者引用指向子类对象 Base_19 * base = new Son_19; base->func(); }
虚析构:解决多态使用时,如果子类有属性开辟在堆区,那么父类指针在释放时无法调用到子类的析构代码。
纯虚析构:和虚析构的区别在于,纯虚析构属于抽象类,无法实例化对象。
有点抽象,直接看例子就懂了,如下声明了一个虚析构,程序可以正常运行和调用,但假如去掉析构的virtual结果如下图
class Animal_21{ public: Animal_21(){ cout<<"animal--构造"<<endl; } virtual ~Animal_21(){ //声明一个虚析构 cout<<"animal--析构"<<endl; } virtual void speak()=0;//声明一个纯虚函数 }; class Cat_21 :public Animal_21{ public: Cat_21(string name){ m_name = new string(name); cout<<"Cat_21--构造"<<endl; } ~Cat_21(){ cout<<"Cat_21--析构"<<endl; if(m_name!=NULL){//m_name开辟在堆区, delete m_name; m_name = NULL; } } virtual void speak(){ cout<<"Cat在说话"<<endl; } string *m_name; }; void test21(){ Animal_21 *animal = new Cat_21("tom");//多态的写法:父类指针指向子类对象 animal->speak(); delete animal; // 若不写虚析构则子类的析构函数将不会被调用 }
最大的问题是:还没等cat的析构调用,程序就已经结束了,然而cat中的m_name是开辟在堆区了,造成了内存泄漏
15.C++文件操作
例:
// 1.包含头文件#include<fstream> // 2.创建文件流 ofstream write_file; // 3.指定打开方式和位置 write_file.open("test.txt",ios::out);//指定以写的方式打开 // 4.写入数据 write_file<<"1.this 是 a test"<<endl; write_file<<"2.this 是 a test"<<endl; write_file<<"3.this 是 a test"<<endl; // 5.关闭文件 write_file.close(); // 读文件 ifstream read_file; read_file.open("test.txt",ios::in); if(!read_file.is_open()){ cout<<"打开失败"<<endl; }else{ // 读数据的3种方式 // 1. // char buf[1024] = {0}; // while(read_file>>buf){ // cout<<buf<<endl; // //这种方式会导致每碰到一个空格,回车就会换行 // } // 2. // char buf[1024] = {0}; // while (read_file.getline(buf,sizeof(buf))) // { // cout<<buf<<endl; // //这种方式的好处就是源文件什么时候换行,读到的就什么时候换行 // } // 3. string buf; while (getline(read_file,buf)) { cout<<buf<<endl; } } read_file.close();
16.模板
1.函数模板:
声明方式:template<typename T> 或者template<class T>
函数模板的调用规则:
1.模板和普通函数都可以调用时,优先调用普通函数
void myprint(int a,int b){ cout<<"normal..."<<endl; } template<class T> void myprint(T a, T b){ cout<<"Template"<<endl; }
2.可以通过空模板参数列表 强制调用模板
//强制调用函数模板 myprint<>(a,b);
//重载 myprint<>(a,b,c);
//当函数模板能更好的匹配时,优先调用函数模板 //编译器自动推到 char d = 'd'; char e = 'e'; myprint(d,e);//因为普通函数参数是int,此时函数模板更好的匹配了
2.类模板
类模板中的成员函数只有在调用的时候才去创建,使用类模板时需要指定类型 与函数模板的区别-->函数模板可以自动推导
声明:
template<typename type1=string,typename type2=int>//类模板中有默认参数,没指定的话就是默认类型 class Person{
public:
};
在调用类模板成员函数的时候必须指定传入数据类型
//第一种写法指定传入类型 void show(Person<string,int> p){ p.show_Person(); } // 第二种写法参数模板化 template<class T1,class T2> void show(Person<T1,T2> &p){ p.show_Person(); } // 3.整个类模板化 template <class T> void show(T &p){ p.show_Person(); }
template <class T> class Base{ //父类为一个类模板 public: T id; }; template <class T1,class T2> class Son:public Base<T2>{ //为了子类的灵活性,将子类也定义为一个类模板 public: Son(T1 name,T2 id){ this->name = name; this->id = id; } T1 name; };
参考资料:
[1] C++ 中的 static - 知乎 (zhihu.com)
[2] C++对象模型详解 - RunningSnail - 博客园 (cnblogs.com)