c++学习笔记

------------------------------------------------------------
  引用的实质类似于c语言中的指针常量

int a=10;
int &b=a;//此句类似于int *const b=&a;

  以此类推到常引用

const int &b=a;//类似于const int *const b=&a;

引用在使用的时候,相当于在其名称前加了一个*(即每次用到b的时候,都等效于*b)。  

因此可以自然的想到,引用的存储类似于指针的存储

  因此下例中,结果为4

#include <iostream>
using namespace std;
struct AA
{
	int &b;
};
int main()
{
	cout<<sizeof(AA)<<endl;
	return 0;
}
------------------------------------------------------------
  1.内联函数在编译时直接将函数体插入到函数调用的地方
  2.inline只是一种请求,编译器不一定允许这种请求

  3.内联函数省去了普通函数调用时压栈、跳转和返回的开销

------------------------------------------------------------

  有参构造函数的调用方法有三种

#include <iostream>
using namespace std;
class Test
{
public:
	Test()
	{
		a=0;
		b=0;
	}
	Test(int aa,int bb)
	{
		a=aa;
		b=bb;
	}
	Test(int aa)
	{
		a=aa;
		b=0;
	}
	void print()
	{
		cout<<"a:"<<a<<endl<<"b:"<<b<<endl;
	}
private:
	int a;
	int b;
};
int main()
{
	//有参构造函数的调用方法有三种
	//1.括号法
	Test t1(1,2);
	t1.print();//a:1	b:2

	//2.等号法
	Test t2=(3,4);//c++对等号符进行了功能增强,此处应用逗号表达式理解
	t2.print();//a:4	b:0

	Test t3=5;
	t3.print();//a:5	b:0
	//前两种方法是c++编译器自动的调用构造函数

	//3.直接调用构造函数	手动的调用构造函数
	Test t4=Test(6,7);//此方法将产生一个匿名对象;此处是t4的初始化,比较:t4=t1(赋值)
	t4.print();//a:6	b:7
	return 0;
}

------------------------------------------------------------

  拷贝构造函数的4种调用机制

#include <iostream>
using namespace std;
class Test
{
public:
	Test()
	{
		a=0;
		b=0;
	}
	Test(int aa,int bb)
	{
		a=aa;
		b=bb;
	}
	Test(const Test& t)
	{
		a=t.a;
		b=t.b;
	}
	void print()
	{
		cout<<"a:"<<a<<endl<<"b:"<<b<<endl;
	}
	int getA()
	{
		return a;
	}
	~Test()
	{
		cout<<"析构函数被调用了"<<endl;
	}
private:
	int a;
	int b;
};
void f(Test &t)
{
	cout<<"a:"<<t.getA()<<endl;
}
Test g()
{
	Test tt(11,22);
	return tt;
}
int main()
{
	//拷贝构造函数的4种调用机制
	Test t1(1,2);
	//1.基本调用1
	Test t2=t1;//注意区别:Test t2;  t2=t1;(赋值操作(赋值构造函数))
	t2.print();

	//2.基本调用2
	Test t3(t1);
	t3.print();

	//3.作为函数参数
	f(t1);

	//4.作为函数返回值  注意:尝试调试体会类的创建与析构流程
	Test t4=g();//用匿名对象初始化t4,此时匿名对象直接转换成t4
	t4.print();
	return 0;
}

  关于第四种,即C++中对象作为函数返回值产生的匿名类详解:

C++中返回值为对象详解

------------------------------------------------------------

  C++中对类和对象的存储管理

#include <iostream>
using namespace std;
class Test
{
public:
	int getA()
	{
		return a;
	}
	void setA(int aa)
	{
		a=aa;
	}
	//成员函数放在代码区中
private:
	int a;//4
	int b;//4
	static int c;//静态成员变量(函数)放在全局数据区
};
int main()
{
	cout<<sizeof(Test)<<endl;//8
	return 0;
}

  1.C++类对象中的成员变量和成员函数是分开存储的
    成员变量:
      普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
      静态成员变量:存储于全局数据区
    成员函数:存储于代码段中

  2.C++中类的普通成员函数都隐式的包含一个指向当前对象的this指针

------------------------------------------------------------

  C++中常常看到类的成员函数后带有const,如

void print() const
{
......
}

  其中const修饰的是this指针,即当前使用的对象的内存空间,因此只能在该函数中,只能进行读的操作,不能对该对象的成员变量进行修改等操作。

假设上例中的函数是在Test类里的,则上述函数等价于void print(const Test *const this),因此,可以推测,对this指针的修改也是不允许的。

若没有加const修饰该函数的话,则相当于void print(Test *const this)

------------------------------------------------------------

C++中的运算符重载
运算符重载有两种方法:全局变量法(友元函数法)与成员函数法
注意:
1.类的成员变量一般为私有属性,因此全局变量使用时,需声明为友元函数

2.前置++与后置++是编译器自动匹配的,如调用c1++这句话时,编译器检测到++是后置的,然后就去找带参数int的重载函数,使用这个重载函数。但如果在程序中只留一个前置++的函数时(删除掉后置++的函数),调用c1++这句话还是可以调用,并且有输出结果,但是有个警告信息

warning C4620: no postfix form of 'operator ++' found for type 'Complex', using prefix form
        C:\Users\Administrator\Desktop\Cpp2.cpp(4) : see declaration of 'Complex'

说明编译器检测到了应该用后置++函数,但是没有后置++函数,所以只能用前置++这个函数

3.现在使用过程中的运算符重载大多使用成员函数法,这更容易理解。而全局函数(友元函数)重载的正确打开方式(用途)--重载输入、输出流,例如要输出一个Complex类,就必须重载输出流,而这通过友元函数法很容易实现,如果要使用成员函数法的话则必须得到ostream类,而这是很困难的

4.友元函数重载运算符常用于运算符的左右操作数类型不同的情况。

如:Complex c1(1,2),c2(3,4);
c2=c1+27;
c2=27+c1;//27不是Complex对象,不能调用函数

5.C++中不能用友元函数重载的运算符:
= () []->

#include <iostream>
using namespace std;
class Complex
{
public:
	Complex(int x,int y)
	{
		a=x;
		b=y;
	}
	
	void print()
	{
		cout<<a<<"+"<<b<<"i"<<endl;
	}


	friend Complex operator+(Complex &c1,Complex &c2);
	Complex operator-(Complex &c2);
	friend Complex& operator++(Complex &c1);
	Complex& operator--();
	friend Complex operator++(Complex &c1,int);
	Complex operator--(int);
	friend ostream& operator<<(ostream &out,Complex &c1);


private:
	int a;
	int b;
};
//成员函数重载+
Complex Complex::operator-(Complex &c2)
{
	Complex temp(this->a-c2.a,this->b-c2.b);
	return temp;
}
//全局函数重载-
Complex operator+(Complex &c1,Complex &c2)
{
	Complex temp(c1.a+c2.a,c1.b+c2.b);
	return temp;
}
//全局函数重载前置++
Complex& operator++(Complex &c1)
{
	c1.a++;
	c1.b++;
	return c1;
}
//成员函数重载前置--
Complex& Complex::operator--()
{
	this->a--;
	this->b--;
	return *this;
}
//全局函数重载后置++
Complex operator++(Complex &c1,int)
{
	Complex temp=c1;
	c1.a++;
	c1.b++;
	return temp;
}
//成员函数重载后置--
Complex Complex::operator--(int)
{
	Complex temp=*this;
	this->a--;
	this->b--;
	return temp;
}
//全局函数重载的用途(重载输出流...)
ostream& operator<<(ostream &out,Complex &c1)//注意ostream是一个类,且并不是Complex的一个成员
{
	out<<c1.a<<" + "<<c1.b<<"i";
	return out;
}
int main()
{
	Complex c1(1,2),c2(3,4);
	
	//全局函数法(友元函数法)
	Complex c3=c1+c2;
	c3.print();//4+6i

	//成员函数法
	Complex c4=c3-c1;
	c4.print();//3+4i

	Complex c5=c2.operator -(c4);
	c5.print();//0+0i

	//全局函数重载前置++
	++c1;
	c1.print();//2+3i

	//成员函数重载前置--
	--c2;
	c2.print();//2+3i

	//全局函数重载后置++
	c1++;
	c1.print();//3+4i

	//成员函数重载后置--
	c2--;
	c2.print();//1+2i

	//重载<<
	cout<<c1<<endl<<c2<<endl;

	return 0;
}

------------------------------------------------------------

继承和组合混搭情况下,构造和析构调用原则
原则:
先构造父类(递归),再构造成员变量,最后构造自己
先析构自己,再析构成员变量,最后析构父类(递归)

------------------------------------------------------------

类型兼容原则:
子类对象可以当做父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象


父类指针可以直接指向子类对象
父类引用可以直接引用子类对象

------------------------------------------------------------

重载重写重定义

函数重载
必须在同一个类中进行
子类无法重载父类的函数,父类同名函数将被名称覆盖
重载是在编译期间根据参数类型和个数决定函数调用
函数重写
必须发生于父类与子类之间
并且父类与子类的函数必须有完全相同的类型
使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
多态是在运行期间根据对象的类型决定函数调用

#include <iostream>
using namespace std;
//重写发生在2个类之间
//重载必须在一个类之间

//重写分为两类
//1.虚函数重写 将发生多态
//2.非虚函数重写(重定义)
class Parent
{
public:
	//这三个函数是重载
	virtual void func()
	{
		cout<<"Parent func() do..."<<endl;
	}
	virtual void func(int i)
	{
		cout<<"Parent func(int i) do..."<<endl;
	}
	virtual void func(int i,int j)
	{
		cout<<"Parent func(int i,int j) do..."<<endl;
	}
	void abc()
	{
		cout<<"Parent abc() do..."<<endl;
	}
};
class Child:public Parent
{
public:
	void abc()
	{
		cout<<"Child abc() do..."<<endl;
	}
	virtual void func(int i,int j)
	{
		cout<<"Child func(int i,int j) do..."<<endl;
	}
	virtual void func(int i,int j,int k)
	{
		cout<<"Child func(int i,int j,int k) do..."<<endl;
	}
};
int main()
{
	Child c1;
	//c1.func();//error
	c1.abc();
	c1.Parent::func();
	return 0;
}

上例中Parent类中三个func()函数为重载,Child类中两个func()函数为重载,而Parent类中func(int i,int j)在子类中进行了重写(有virtual关键字修饰),Parent类中的abc()函数则在子类Child中进行了重定义(没有virtual关键字修饰)

main函数中的error处原因:

子类无法重载父类的函数,父类同名函数将被名称覆盖
C++编译器看到func名字在子类中已经存在了,所以不会去父类中去找而在子类中没有无参的func函数,所以报错
解决方法-----使用域作用符:c1.Parent::func();


------------------------------------------------------------

产生多态时vptr指针的分布初始化

#include <iostream>
using namespace std;
class Parent
{
public:
	Parent(int a=0)
	{
		this->a=a;
		print();//问题由来:构造函数中调用了虚函数
			//此处是调用父类的构造函数还是子类的构造函数?
	}
	virtual void print()
	{
		cout<<"I'm Parent"<<endl;
	}
private:
	int a;
};
class Child:public Parent
{
public:
	Child(int a=0,int b=0):Parent(a)
	{
		this->b=b;
		print();
	}
	virtual void print()
	{
		cout<<"I'm Child"<<endl;
	}
private:
	int b;
};
void howToPrint(Parent *p)
{
	p->print();
}
int main()
{
	Child c1;
	//vptr指针的分布初始化
	//当执行父类的构造函数时,c1.vptr指向父类的虚函数表
	//当父类的构造函数运行完毕后,会把c1.vptr指向子类的虚函数表

	return 0;
}

上例中,当执行父类的构造函数时,c1.vptr指向父类的虚函数表,当父类的构造函数运行完毕后,会把c1.vptr指向子类的虚函数表,因此结果为
I'm Parent
I'm Child


说明:通过虚函数表指针vptr调用重写函数实在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数,而普通成员函数时在编译时就确定了调用的函数,在效率上,虚函数的效率要低很多
出于效率考虑,没有必要将所有得到成员函数都声明为虚函数
上例中,C++编译器执行howToPrint函数,不需要区分是子类对象还是父类对象


------------------------------------------------------------

函数模板中,用友元函数重载<<>>操作符
friend ostream& operator<<<T>(ostream &out,Complex<T> &c3);


类模板中的类写成.h  .cpp形式时,应该在测试使用中包含.cpp而不是.h


------------------------------------------------------------

C++中的类型转换
static_cast 静态类型转换,如int转换成char
reinterpret_cast 重新解释类型
dynamic_cast 命名上理解是动态类型转换,如子类和父类之间的多态类型转换
const_cast 字面上理解就是去const类型
4种类型转换格式:
TYPE B=static_cast<TYPE>(b)


------------------------------------------------------------

关于异常

1.如果接受异常的时候使用一个异常变量,则拷贝构造异常变量
2.如果接受异常的时候使用一个异常变量的引用,则使用throw时候的那个变量
3.catch中不能存在同类型的变量和引用


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hober.z

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值