C++学习总结2

class MyObj
{
public:
MyObj(int x) : x_(x){}
void setValue(int x) { x_ = x;}
void show(void)
{
cout<<x_<<endl;
}
private:
int x_;
};

MyObj fun(void)
{
return MyObj(4); //直接生成一对象,并返回;
}

int main()
{
MyObj obj = fun();
obj.show();
return 0;
}

//面向对象编程关系中,组合的简单用法:
#include
#include

using namespace std;

class A
{
public:
A(int x) : x_(x){cout<<“A constructor”<<endl;}
private:
int x_;
};

class B
{
public:
B(int y,int xx) : y_(y),a1(xx){cout<<“B constructor”<<endl;}
private:
int y_;
A a1;
};

class C
{
public:
C(int y,int xx) : y_(y),a1(xx),b1(4,6){cout<<“C constructor”<<endl;}
private:
int y_;
A a1;
B b1;
};

int main()
{
C c(22,33);
return 0;
}
// output message
// A constructor
// A constructor
// B constructor
// C constructor

3)委托关系的应用案例
#include
#include

using namespace std;

//委托:可以根据接口和实现分离,根据不同需求切换不同实现版本;
class A; //声明类A;
class B
{
friend class A;
public:
B(int y) : y_(y){cout<<“B constructor”<<endl;}
A* aptr;
private:
int y_;
};

class A
{
public:
A(int x) : x_(x){cout<<“A constructor”<<endl;}
void show(void){cout<<“a class handle—”<<endl;}
private:
int x_;
};

int main()
{
A a(88);
B bb(76);
bb.aptr->show();
return 0;
}

4)转换函数 operator type(),应用案例:
//operator type() 转换函数的一般形式
class Fraction {
public:
Fraction(int num, int den) :
m_numerator(num), m_denominator(den) {}

operator double() const //转换函数的写法,不是必须基本类型,自定义类型也可以,只要认为可以转换即可
{
    return (double) (static_cast<float>(m_numerator) / static_cast<float>(m_denominator));
}

private:
int m_numerator; // 分子
int m_denominator; // 分母
};

Fraction f(3, 5);
double d = 4 + f; // 调用operater double将f转换成double

5)non-explicit one-argument constructor:(explicit在构造函数中的用法)
class Fraction
{
public:
explicit Fraction(int num,int den = 1) : m_numerator(num),m_denominator(den){}
Fraction operator+(const Fraction& f)
{
Fraction tmp(0,0);
tmp.m_numerator = this->m_numerator + f.m_numerator;
tmp.m_denominator = this->m_denominator + f.m_denominator;
return tmp;
}
private:
int m_numerator;
int m_denominator;
};

int main()
{
Fraction f(3,5);
Fraction d = f + 4; //如果构造函数添加explicit,则此句错误,禁止将4转换为Fraction对象;
return 0;
}

6)智能指针的内部代码剖析
//产生的对象像一个个指针,产生的对象像一个函数;
//即智能指针和仿函数;

template
class Myshared_ptr
{
public:
T& operator*() const
{ return px; }
T
operator->() const
{ return px;}
Myshared_ptr(T* p) : px§{}
private:
T* px;
};
struct Foo
{
void method(void)
{
cout<<“method handle”<<endl;
}
};

int main()
{
Myshared_ptr sp(new Foo);
sp->method();//相当于px->method();
return 0;
}

7)仿函数的使用案例:
class A
{
public:
int operator()(int x)
{
return (3 + x);
}
};

int main()
{
A a;
cout<<a(6)<<endl;
cout<<A()(8)<<endl; //注意:A()是一个临时对象,之后调用运算符重载;
return 0;
}
//输出是9和11;

8)namespace 的用法
namespace jj01
{
void printMsg1(void)
{
cout<<“jj01-------”<<endl;
}
} //注意,这里没有分号;

namespace jj02
{
void printMsg1(void)
{
cout<<“jj02---------”<<endl;
}
}
int main()
{
jj01::printMsg1();
jj02::printMsg1();
return 0;
}

9)模板特化及偏特化的的使用案例和意义:
//泛化
template
struct myhash{ void show(){cout<<“general --”<<endl;}};

//特化(在这里叫全特化更准确)
//显示特化(全特化)的意义:当模板参数是需要特别处理时,需要显示特化
//偏特化的意义:当模板参数符合一部分条件的时候,做处理;
template<>
struct myhash
{
size_t operator()(char x) const
{
cout<<x<<endl;
return 0;
}
};

int main()
{
myhash()(‘u’);
return 0;
}

9-2)偏特化的使用场景
//下面偏特化的应用场景是:当如果传入一个指针的时候,会选择偏特化;
//泛化
template
struct Foo
{
public:
void show(void)
{
cout<<i<<endl;
}
private:
const int i = 33;
};

//偏特化
template
struct Foo<T*>
{
public:
void show(void)
{
cout<<i<<endl;
}
private:
const int i = 2;
};

int main()
{
Foo<int*> f1;
f1.show(); //调用的是偏特化模板类
Foo f2;
f2.show(); //调用的是泛化的模板类
return 0;
}

10)可变参数模板
//可变参数模板的语法
//下面两句话的意思是告诉编译器,我的模板参数个数是可变的,
//我的函数参数个数也是可变的;
template<typename… Args>
void fun(Args… args)
{

}

int main()
{
fun(1,2,“hello”);
fun(1,4.6);
fun(33);
return 0;
}

//经典可变模板参数案例:
void fun(void)
{
cout<<“no paramter”<<endl;
}

template<typename T, typename… Args>
void fun(const T& firstArg,Args… args)
{
cout<<firstArg<<endl;
fun(args…);
}

int main()
{
fun(34,“hhlo”,23.67);
return 0;
}

11)指针和引用的区别
int x = 0;
int* p = &x; //p指向x;
int& r = x; //r代表x;
reference 就是代表的关系;

object和reference的大小相同,地址相同(全都是假象)
int main(void)
{
int x = 0;
int& r = x; //r代表x;
cout<<sizeof(x)<<endl; //4
cout<<sizeof®<<endl; //4,但其实底层逻辑是指针,所以在64bit里面是8;
cout<<&x<<endl; //0x7ffef291eb0c
cout<<&r<<endl; //0x7ffef291eb0c
return 0;
}

12)多态的应用案例:
//对象是猪,指针声明的时候是动物,(向上转型)
class Shape
{
public:
virtual void draw(void) = 0;
};

class Circle : public Shape
{
public:
virtual void draw(void)
{
cout<<“draw circle—”<<endl;
}
};

class Square : public Shape
{
public:
virtual void draw(void)
{
cout<<“draw square—”<<endl;
}
};

void fun(Shape* obj)
{
obj->draw();
}

int main(void)
{
Circle c1;
Square s1;
fun(&c1);
fun(&s1);
return 0;
}

13)带const的成员函数和不带const的成员函数是不同成员函数签名;
当成员函数有const版本和非const版本的时候,
const object调用的是带有const版本的成员函数,
非const object调用的是非const版本的成员函数;
class Test
{
public:
void show(void){cout<<“hello world”<<endl;}
void show(void)const {cout<<“const hello world”<<endl;}
};

int main(void)
{
Test t1;
t1.show();

const Test t2;
t2.show();
return 0;

}

14)C++对象的内存布局:影响C++对象大小的因素
静态成员变量、函数独立于单个实例化对象,其中,函数包括成员函数,虚函数,静态成员函数等;
影响C++对象大小的三个因素:非静态数据成员,虚函数和字节对齐;
//虚表指针和type_info
class A
{
public:
A(){}
virtual ~A(){}
// virtual void show(void){}
void show(){}
static void fun(){}
};

int main(void)
{
cout<<sizeof(A)<<endl;
cout<<sizeof(float)<<endl;
return 0;
}

C++规定空类对象大小至少为1个字节,只是为了区分实例化对象;
例如可以创建多个空类的对象,可以通过对象的内存地址区分;

非静态的数据成员在内存的布局和地址增长是一致的;

15)设计模式
//23种设计模式(巧记方法:利用的是同音或者谐音)
//创公园,但见愁 //5种 创建型模式 : 工厂模式,原型模式,单例模式,建造者模式,抽象工厂模式;
//姐想外租,世代装桥 //7种 结构型模式 : 享元模式,外观模式,组合模式,适配器模式,代理模式,装饰器模式,桥接模式;
//形状折中模仿,戒备观测鸣笛 //11种 行为型模式 : 状态模式、责任链模式、中介模式、模板模式、访问者模式;解释器模式、备忘录模式、观察者模式、策略模式、命令模式、迭代器模式;

保持类的完整性是非常重要的;
①如果一个类里面 某个成员对象有nontrivial default constructor,编译器就会为我们的类产生nontrivial default constructor。
那么编译器这样做的理由是什么?
答案是因为类成员对象有nontrivial default constructor,那么编译器就需要 显式的来调用这个类成员对象的nontrivial default constructor。而编译器想显式的调用类成员对象的nontrivial default constructor,就需要自己来合成一些代码来调用。但是记住,编译器合成的nontrivial default constructor 仅仅调用类成员对象的默认构造函数,而不对我们类里面的其它变量做任何初始化操作。
也就是说,如果你想初始化类成员变量以外的变量例如一个int、一个String,那么必 须自己定义默认构造函数来完成这些变量的初始化。而编译器会对你定义的默认构造函数做相应的扩展,从而调用类成员对象的nontrivial default constructor。
②如果一个派生类的 基类有nontrivial default constructor,那么编译器会为派生类合成一个nontrivial default constructor。
编译器这样的理由是:因为派生类被合成时需要显式调用基类的默认构造函数。
③如何一个类里面隐式的含有 任何virtual function table(或vtbl)、pointer member(或vptr)。
编译器这样做的理由很简单:因为这些vtbl或vptr需要编译器隐式(implicit)的合成出来,那么编译器就把合成动作放到了默认构造函数里面。所以编译器必须自己产生一个默认构造函数来完成这些操作。
所以如果你的类里带有任何 virtual function,那么编译器会为你合成一个默认构造函数。
④如果一个类虚继承于其它类。
编译器这样做的理由和③类似:因为虚继承需要维护一个类似指针一样,可以动态的决定内存地址的东西(不同编译器对虚继承的实现不仅相同)。
那么 除了以上四种情况,编译器并不会为我们的类产生默认构造函数。
所以编程中切忌想当然,要明白哪些事情是编译器做的,哪些事情需要程序员来完成的。就像堆所占用的资源需要程序员自己来释放,而栈空间是编译器管理的一样。
只有如此,才能编写出质量更高的代码。

2023-7-24
1)类的成员函数不占用类对象的内存空间
2)成员函数不占用对象空间,是跟着类走,每个类只诞生一个;
3)静态成员变量,保存在对象外面的,表示所占用的内存空间和对象无关;
4)只要类里至少有一个虚函数,就会产生一个虚表指针,这个指针正好指向虚函数表;
5)如果类中有多个虚函数,就会有多个虚函数指针,这些指针需要有地方放,所以就会放进一个表格里,这个表格我们称作为虚函数表;
这个虚函数表是编译到可执行文件中的,在程序执行的时候会载入内存中来;
虚函数表是基于类的,跟着类走的;(注意:在类对象中,因为有虚函数的存在,会在类对象中添加一个虚表指针,可不是一个虚函数表!!!);
总结:虚函数不计算类对象sizeof()里面的,虚函数表是基于类的,虚表指针是基于类对象的!!!

6)this指针调整:多重继承,继承顺序,决定着后期的内存布局;
说白了就是一句话,当调用A对象成员函数的时候,this指针会调整到指向A对象的成员函数;当调用B对象成员函数,this指针会调整到指向B对象的成员函数;
class A
{
public:
A()
{
printf(“A::A()的this指针是: %p\r\n”,this);
}
void funcA()
{
printf(“A::funcA()的this指针是: %p\r\n”,this);
}
int a;
};

class B
{
public:
B()
{
printf(“B::B()的this指针是: %p\r\n”,this);
}
void funcB()
{
printf(“B::funcB()的this指针是: %p\r\n”,this);
}
int b;
};

class C : public B,public A
{
public:
C()
{
printf(“C::C()的this指针是: %p\r\n”,this);
}
void funcC()
{
printf(“C::funcC()的this指针是: %p\r\n”,this);
}
int c;
};

//合成构造函数条件1:
该类没有构造函数,但该类包含一个有对象类型的程序,此对象成员有构造函数,这个时候,编译器会合成一个构造函数,合成的目的是为了调用对象成员的构造函数;

程序转换语义:
1)定义时初始化对象:
X x3 = (x0);
编译器会对上面的语句,会拆分为两行,代码类似如下:
X x3;先定义一个x3对象;
x3.X::X(x0); 再调用拷贝构造函数;

2)参数的初始化
class CValue
{
public:
int val1;
int val2;
public:
CValue(int v1 = 0,int v2 = 0) : val1(v1),val2(v2){cout<<“调用了构造函数”<<endl;}
CValue(const CValue& obj) : val1(obj.val1),val2(obj.val2) //注意拷贝构造函数也用了初始化列表
{
cout<<“调用了copy构造函数”<<endl;
}
~CValue(){cout<<“调用了析构函数-----”<<endl;}
};

CValue func(CValue& obj) //不写copy构造函数,此函数编译错失;2)用引用太重要了,要不会多一次构造函数
{
// CValue tmp;
// tmp.val1 = obj.val1 * 2;
// tmp.val2 = obj.val2 * 2;
// return tmp;
return CValue(obj.val1 * 2,obj.val2 * 2); //上面四句话可以改进为此条语句,如果写上面四条语句,编译器也会优化为此条语句
}

int main()
{
CValue value(2,4);
auto e = func(value);
return 0;
}

2)类型转换构造函数
class X
{
public:
explicit X(int x) : x_(x){cout<<“X构造函数执行”<<endl;}
public:
int x_;
};

int main()
{
X x1(20);
// X x2 = 10; //类型转换构造函数,(涉及到隐式类型转换)
return 0;
}

3)深入C++之逐位拷贝(bitwise copy);
如果只是一些简单的成员变量类型,如int double,则根本不要拷贝构造函数,编译器内部就支持成员变量的bitwise(按位)拷贝;
如果增加了自己的拷贝构造函数,就会导致编译器的bitwise拷贝能力失效,所以如果增加拷贝构造函数的话,就要自己负责初始化了;
class X
{
public:
explicit X(int x,float mm) : x_(x),mm_(mm) {cout<<“X构造函数执行”<<endl;}
//如果增加下面的拷贝构造函数,则bitwise(按位拷贝)会失效;
// X(const X& obj) : x_(obj.x_),mm_(obj.mm_){cout<<“X拷贝构造函数执行”<<endl; }
public:
int x_;
float mm_;
char a;
};

int main()
{
X x1(20,67.23);
x1.x_ = 10;
x1.mm_ = 90.8;

    X x2(x1);
    cout<<x2.x_<<endl;
    cout<<x2.mm_<<endl;
    return 0;

}

4)单纯的类不纯时引发虚函数调用问题:
可以用memset(this,0,sizeof(X))代替构造函数,memcpy(this,&tm,sizeof(X))可以代替拷贝构造函数;
class X
{
public:
int x_;
int y_;
int z_;
X()
{
memset(this,0,sizeof(X)); //执行此条语句,会导致new出来的对象,调用函数失败;
cout<<“X的构造函数执行”<<endl;
}
X(const X& obj)
{
memcpy(this,&obj,sizeof(X));
cout<<“X的构造构造函数执行”<<endl;
}
virtual void fun(void)
{
cout<<“fun函数执行”<<endl;
}
virtual ~X()
{
cout<<“虚析函数执行----”<<endl;
}
};

int main()
{
X x0;
x0.fun(); //直接调用,属于静态连遍,跟多态没有关系,所有即使虚函数指针清0,也不会影响调用;

    //  X* px0 = new X();  //因为在构造函数中memset的存在,会导致下面语句调用段错误,因为有指针,会有多态发生;
    //  px0->virfun();//通过虚函数指针,找虚函数表,然后通过虚表找到虚函数的地址并调用,但由于虚表指针被清空,所以会报错;
    //  delete px0;

    return 0;

}

如果没有继承关系,虚函数和普通函数,老师认为没有实际区别;
class X
{
public:
int x_;
int y_;
int z_;
X()
{
memset(this,0,sizeof(X)); //执行此条语句,会导致new出来的对象,调用函数失败;
cout<<“X的构造函数执行”<<endl;
}
X(const X& obj)
{
memcpy(this,&obj,sizeof(X));
cout<<“X的构造构造函数执行”<<endl;
}
virtual void virfun(void)
{
cout<<“virfun函数执行”<<endl;
}
void ptfun(void)
{
cout<<“fun函数执行”<<endl;
}
virtual ~X()
{
cout<<“虚析函数执行----”<<endl;
}
};

int main()
{
X x0;
x0.virfun();

    X& x1 = x0;
    x1.virfun();

    X* px0 = new X();
    px0->virfun();
    return 0;

}

  1. 静态类型和动态类型
    静态类型是定义的类型;
    动态类型是对象目前所指向的类型(运行的时候才决定的类型),只有指针和引用才有动态类型;
    而且是父类指针指向子类对象;
    一个变量,有静态类型,也可以动态类型,也可以没有动态类型;

6)静态绑定和动态绑定
静态绑定:绑定的是静态类型,所对应的函数或者属性依赖于对象的静态类型,发生在编译期;
动态绑定:绑定的是动态类型,所对应的函数或者属性依赖于对象的动态类型,发生在运行期;
a)普通成员函数是静态绑定,虚函数是动态绑定;
b)缺省参数一般都是静态绑定;
结论:不应该在子类中定义一个继承来的非虚函数;
结论2:不要重新定义虚函数的缺省参数值;

7)多态的体现
从代码实现上:如果走虚函数表,就是多态,否则不是;
从表现形式上:有继承关系,父类必须有虚函数,(也就是说子类中一定有虚函数),派生类重写父类虚函数;

猜你喜欢

转载自blog.csdn.net/qq_30143193/article/details/131931445
今日推荐