1,静态成员函数中能访问非静态成员(包括属性和函数)吗?
不能直接访问。
因为所以对非静态成员的访问都是通过this指针,而静态成员函数中没有this
2,静态成员函数中能访问静态成员(包括属性和函数)吗?
可以
静态成员属于类,不属于对象,不需要通过 对象去访问
3,静态成员函数可以是常函数吗?
不可以
常函数的 const是限定 *this 的 (不能通过 this去修改它指向的对象),但是静态成员函数中没有this
和静态成员变量一样,也可以通过类名去访问静态成员函数
对象名.函数名(参数);
或者
类名::函数名(参数);
4,为什么C++中不用C语言的malloc?
解:c++中也可以使用 malloc/free ,但是有缺陷:
当你 用malloc 分配一个 class类型的对象时,不会自动调用构造函数
且用 free 释放一个 堆区 的 class类型的对象时,不会自动调用析构函数
所以建议 c++不要使用 malloc/free
c++专门提供了两个运算符 new / delete:
new 是用来分配堆区空间的
delete 是用来释放堆区空间的
new 一个对象
类型 * 指针名 = new 类型;//没有初始化
或者
类型 * 指针名 = new 类型(初始值);//初始化
delete 一个对象
delete 指针变量名;
new 一组对象
类型 * 指针名 = new 类型[元素个数];
或者
类型 * 指针名 = new 类型[元素个数]{初始值,初始值.....};
初始值的个数可以少于等于元素个数
delete 一组对象
delete [] 指针变量名;//不加 [] 不会报错,但是只会释放一个对象。
5,引用
(1)引用作为函数参数:调用函数传参时,引用传递比值传递的效率高(就自定义类型而言),因为引用传递不需要为形参分配空间,也不需要调用构造类型的构造函数。
(2)引用作为返回值:这种情况下,函数调用表达式可以作为左值。
(3)常引用:用const修饰的引用,常引用不能修改。一般用于函数参数,不能通过形参去修改实参的值,限制它只能进行读访问。
(4)引用的底层实现:可能是一个指针
6,默认的拷贝构造函数
//默认生成的拷贝构造函数长这样,举例:
Student(const Student &other)
{
this->m_id = other.m_id;
this->m_age = other.m_age;
this->m_name = other.m_name;
this->m_score = other.m_score;
cout << "student拷贝构造函数"<< endl;
}
深拷贝和浅拷贝:编译器默认生成的那种逐个成员赋值的形式脚浅拷贝,浅拷贝有局限性:当类中有指针类型成员时,该指针应该指向一个堆区空间。此时用浅拷贝去初始化一个新对象的话,那么新对象和原对象的指针成员都会指向同一堆区空间,当一个对象对该内存空间进行修改时,另一个对象也被动修改了,当对象销毁时,都是调用的析构函数对同一堆区空间进行销毁,会造成重复delete。所以,当类的成员有指针时,需要自己实现拷贝构造函数,也就是深拷贝。
7,右值引用和移动构造
(1)c++11标准增加了右值引用的概念
前面讲的引用 ->左值引用, c++11之前 只能用 常量引用
(2)右值引用语法格式:类型 && 右值引用名 = 右值对象;//&&是右值引用声明符
(3)右值对象:如,常量,临时对象,快要销毁的对象.....
(4)右值引用主要是用于移动构造函数。
(5)前面讲的拷贝构造的例子,有一种情况:
原有对象将要销毁,那么原有对象的资源将要释放,而新对象又需要重新分配资源。
为什么不直接把原有对象的资源给新对象?
于是,右值引用就派上了用场。
(6)移动构造函数语法:移动构造函数只有一个显式参数:本类对象的右值引用
函数名/类名(类名 &&other)
{}
8,运算符重载
(1)重载为静态成员函数会报错,因为静态成员函数没有this指针,不能访问非静态成员。所以,运算符重载为非静态成员函数时,要比重载为友元函数少一个显示参数(有一个隐形的this)调用运算符重载函数时,运算符左边的操作数地址传递给this,右边的操作数传递给唯一的那个参数。s1+s2=>s1.operator+(s2)
对于既可以重载为成员函数又可以重载为友元函数的运算符而言,只需要实现一个版本即可,否则会造成二义性,报错。
(2)重载时,返回值是返回*this还是引用?
解:可以返回引用,因为返回的对象是*this,该对象的生存期并不是随代码块结束的,并且能返回引用就返回引用,因为返回引用比返回值的效率要高些。
(3)C++中,重载输出运算符,用的是C++中用于标准输出的类ostream,它有唯一的对象cout,ostream类中重载了很多次<<运算符。
如:
//int类型数据输出
ostream& ostream::operator<<(int data)
{
printf("%d",data);
}
//double类型数据输出
ostream& ostream::operator<<(double data)
{
printf("%lf",data);
}
(4)为啥都是返回ostream的引用?
解:因为ostream的类只允许创建一个对象,如果返回值,就会创建临时对象,会失败。
(5)为啥不能是void?
解:因为需要支持连续输出。
(6)现在需要重载为自定义类型Mystring,该怎么重载?
A 重载为 ostream 类的成员函数
B 重载为 ostream 类的友元函数
C 重载为 Mystring的成员函数
D 重载为 Mystring的友元函数
解:AB都不可取,因为需要修改ostream类中的代码,而它是标准库中的代码,不能修改。C的话意味着两个参数都是Mystring对象,不对。因为正确的参数应该是第一个是Mystring对象,第二个是ostream对象=> s1 << out ,但是s1 << cout << endl; //太奇怪了,不符合大家的习惯;所以第四个才是对的,也就是ostream& operator<<(ostream& out,const Mystring &s);
9,C++11标准中的关键字:delete,default,explicit
编译器在没有自己实现函数时会自动为类生成一些函数,当我们不需要某个自动默认生成的函数时就可以用delete;反之,希望自动生成时,就用default;
explicit禁止隐式转换。
如:何为隐式转换呢?
Mystring s2 = "world";
= 左边是 Mystring 类型
= 右边是 const char * 类型
两个类型不一致,怎么能赋值呢?
所以,先用 "world" 转换为一个 Mystring的临时对象,然后用这个临时对象初始化 s2
所以建议尽量不要向上面的写法,而应该按照下面的写法:
Mystring s2("world");
如果不想要允许进行这样的转换,就在构造函数前加上 explicit关键字即可
10,赋值运算符
当用一个已经存在的对象给另一个已经存在的对象赋值的时候,会调用赋值运算符重载函数(拷贝赋值函数),一定要注意和初始化的区别(初始化调用拷贝构造)。如果程序员没有自己实现拷贝赋值函数,编译器会自动生成一个(逐个成员赋值,浅拷贝)如果需要执行深拷贝,就得自己写。
注意:
拷贝赋值函数只能重载为成员函数,如果你重载为友元函数,编译器会为你自动生成一个拷贝赋值的成员函数就会产生二义性 ,报错。
11,移动赋值:用一个右值对象给一个已经存在的对象赋值时会调用移动赋值函数
12,小总结:
默认情况下,一个空类会自动生成:
原型 调用时机
默认构造函数 Mystring(); 构造对象时不传参数
析构函数 ~Mystring(); 对象被销毁
拷贝构造函数 Mystring(const Mystring &); 用一个已有的对象初始化一个新对象
拷贝赋值函数 Mystring& operator=(const Mystring &); 用一个已有的对象为另个已有对象赋值
c++11还有两个
移动构造函数 Mystring(Mystring &&); 用一个已有的右值对象初始化一个新对象
移动赋值函数 Mystring& operator=(Mystring &&); 用一个已有的右值对象为另一个已有对象赋值
13,下标运算符[] :必须要重载为成员函数
双目运算符:要两个操作,第一个参数是某个对象
第二个参数是整数:[0,n-1]
以 Mystring为例:
char& Mystring::operator[](int index)
{
return str[index];
}
14,常对象,用const修饰的对象
常对象只能调用常函数,不能调用非 常函数
以Mystring为例
如果有个常对象 s1, 不能调用非常函数, cout << s1[1] << endl;报错
但是逻辑上是有这个需求的,允许常对象对其成员进行读访问
怎么解决?
给 operator[] 函数加上 const?
那么 s1[1] = 'X' 这句话也不会报错了,-》那么就对常对象进行修改了,也有问题!!!!
重载两次:
char& operator[](int index); //给普通对象调用,返回引用,可读可写
const char& operator[](int index)const; //给常对象调用,返回常引用,只读不可写
15, ++ -- 运算符
a++ ++a
对于a本身而言两者并没有区别,区别在于表达式的值不一样, a++ 这个表达式的值是 a自增之前的值,
++a这个表达式的值是 a自增之后的值。
++ 是单目运算符,有 前++ 和 后++之分,重载一次显然满足不了要求,-》需要重载两次
后++ 要提供一个 “占位形参”,只是起到占位置的作用(用于重载),函数内部并不会用到它,调用函数时,
也不需要为这个形参传递实参
operator++(int)
operator++()
16,函数调用运算符重载 ()
如果一个类中重载了 () ,那么这个类的对象 被称之为 函数对象/仿函数
-》因为他的行为类似函数,可以调用。
例子:
class Less
{
public:
Less(int data =0)
{
m_data = data;
}
bool operator()(int x)
{
if(x < m_data)
return true;
else
return false;
}
private:
int m_data;
};
int main()
{
Less l(80);//因为 Less重载了() ,所以 l就是函数对象/仿函数
bool b = l(90);
if(b)
{
cout << "l >= 90" <<endl;
}
else
{
cout << "l < 90" <<endl;
}
}
函数重载只能给已有的运算符赋予新的功能,不能创造一个新的运算符 ,比如: @ # $
大部分已有的运算符可以被重载,但是也有一些运算符是不能被重载的:
. 用于访问对象中的成员
:: 作用域运算符
?: 问号运算符
sizeof
.*
17, new/delete 和 malloc/free 的区别
> new 和 malloc都是在堆中分配空间
> new 是堆中创建一个对象,并返回其指针。“类型”,“调用该类型的构造函数”
> malloc仅在堆中分配一块空间,并返回其地址。 “无类型”
> new的时候,会调用对象的构造函数;malloc不会
> new分配的对象,销毁时必须用delete
> malloc分配的空间,释放时必须用free
> new/delete是运算符, malloc/free是一个库函数
18,继承
一个派生类对象中包含两个部分
1,从基类继承过来的部分 (基类子对象)
2,派生类自己新增的部分
由于派生类对象中包含了基类子对象,那么我们能不能把派生类对象当做基类对象使用呢?
可以的
前提是继承方式是公有继承
反过来不行的:也就是基类对象不能当做派生类对象使用
公有继承的派生类对象可以当做基类对象使用,具体体现在:
可以使用派生类对象初始化基类对象
可以使用派生类给基类对象赋值
基类的引用可以绑定派生类对象
基类 &引用名 = 派生类对象;
基类的指针可以指向派生类对象
基类 *指针名 = &派生类对象;