本章学习内容:
1.const
const和define宏区别
const在C++中为真正常量.示例:
const int c = 0; //const局部变量
int* p = (int*)&c; //会给p重新分配空间,而c还是处于常量符号表中
*p = 5; //此时修改的值是新的地址上,对于c而言,依旧为0
printf("c = %d,*p=%d\n", c,*p); //打印: c = 0, *p=5
2.指针const 1) 底层const(位于*左侧) const int *p : const修饰*p为常量,也就是说该指针指向的对象内容是个常量,只能改变指向的地址.但是可以通过其他方式修改对象内容 例如:
int a=1,a=2;
const int *p = &a;
*p = 2; //error, 不能直接修改
a=2; //right,通过对象本身修改内容
*p = &b; //right,可以指向其它地址
2) 顶层const(位于*右侧) int * const p : const修饰指针p是个常量,也就是说p指向的地址是个常量 例如:
int a=1,a=2;
int * const p = &a;
p = &b; //error,p指向的地址是常量,永远为a地址,不能修改
注意:顶层const变量可以替代mutable变量
3.inline内联函数
示例如下:
inline int MAX(int a, int b)
{
return a > b ? a : b ;
}
注意:当内联函数里的代码过多,且流程复杂时,编译器可能会拒绝该函数的内联请求,从而变成普通函数
4.函数重载 参数表不同主要有以下几种
注意:
5.extern “C” 可以实现调用C库代码. 示例:
#ifdef __cplusplus
extern "C" //通过C方式来编译add.h,也就是add()函数
{
#include "add.h"
}
#endif
6.new/delete声明与释放
示例如下:
int *p = new int(); //默认值为0
int *p1= new int(1); //动态分配一个int空间给p1,并赋值为1
float *p2=new float(2.0f); //2.0后面加f,表示2.0是个float类型
int *p3 = new int; //默认值为随机值
string *p4 = new string[10];
delete p;
delete p1;
delete p2;
delete p3;
delete[] p4;
注意: • 释放数组的空间时,必须使用delete[],而不是delete,避免内存泄漏
7.namespace命名空间
示例:
#include <stdio.h>
namespace First //定义First命名空间
{
int i = 0;
}
namespace Second //定义Second命名空间
{
int i = 1;namespace Internal //在Second里,再次定义一个Internal空间(实现嵌套)
{
struct Position
{
int x;
int y;
};
}
}
int main()
{
using namespace First; //使用First整个命名空间,成为该main()的默认空间
using Second::Internal::Position; //使用Second->Internal空间里的Position结构体
printf("First::i = %d\n", i);
printf("Second::i = %d\n", Second::i);
Position p = {2, 3};
printf("p.x = %d\n", p.x);
printf("p.y = %d\n", p.y);
return 0;
}
输出结果:
First::i = 0
Second::i = 1
p.x = 2
p.y = 3
8.C++中的4种转换 static_cast(静态类型转换) 用于变量和对象之间的转换,比如(bool,char,int等) 用于有继承关系的类对象指针转换,可以通过父类对象去初始化子类对象(注意只能初始化父类的那部分)
const_cast(去常类型转换) 常用于去除const类对象的只读属性 强制转换的类型必须是指针*或者引用&
示例-去除const对象的只读属性:
class Test
{
public:
int mval;
Test():mval(10)
{
}
};
int main()
{
const Test n1;
//n1.mval = 100; //error,不能直接修改常量对象的成员
Test *n2 = const_cast<Test *>(&n1); //通过指针*转换
Test &n3 = const_cast<Test &>(n1); //通过引用&转换
n2->mval = 20;
cout<<n1.mval<<endl; //打印20
n3.mval = 30;
cout<<n1.mval<<endl; //打印30
}
dynamic_cast(动态类型转换) 只能用在有虚函数的类中,一般在多重继承下用的比较多,比如:
class BaseA
{
public:
virtual void funcA()
{
cout<<"BaseA: funcA()"<<endl;
}
};
class BaseB
{
public:
virtual void funcB()
{
cout<<"BaseB: funcB()"<<endl;
}
};
class Derived : public BaseA,public BaseB
{
};
int main()
{
Derived d;
BaseA *pa=&d;
pa->funcA(); //打印 BaseA: funcA()
/*通过强制转换执行*/
BaseB *pb=(BaseB *)pa;
pb->funcB(); //还是打印BaseA: funcA(), 因为pb还是指向pa,执行的还是pa的虚函数表
/*通过dynamic_cast执行*/
pb = dynamic_cast<BaseB *>(pa);
pb->funcB(); //打印 BaseB: funcB()
//编译器会去检测pa所在的地址,发现有多个虚函数表,然后根据 <BaseB *>来修正指针pb
return 0;
}
reinterpret_cast(解读类型转换) 对要转换的数据重新进行解读,适用于所有指针的强制转换
9.拷贝构造函数 一般用于当类对象中有成员指针时,才会自己写拷贝构造函数,因为编译器自带的默认拷贝构造函数只支持浅拷贝
class Test
{
//... ...
Test(const Test& t)
{
//copy... ...
}
};
10.构造函数初始化列表
示例如下:
class Example
{
private:
int i;
float j;
const int ci;
int *p;
public:
Test(): j(1.5),i(2),ci(10),p(new int(3)) //初始化i=2,j=1.5,ci=10 *p=3
{
}
};
11.析构函数 注意:
构造函数的调用顺序
12.const成员函数
13.const对象
14.栈、堆、静态存储区的区别 栈 用来存放函数里的局部变量,当调用某个函数时(执行某个代码段),会将该函数的变量(从数据段读出)入栈,然后退出函数的时候,会将该局部变量出栈进行销毁. 一般如果局部变量未初始化的话,都是随机值 堆 堆由程序员分配释放new/delete,所以需要注意内存泄漏问题 一般new分配的对象变量,其成员都是随机值 静态存储区 用来存放全局变量,一直会存在的,一般编译器为自动将未赋值的全局变量进行一次清0
15.静态成员变量/静态成员函数
示例如下:
class Test{
private:
static int mval;
public:
Test()
{
print();
}
static int print() //静态成员函数是存在代码段中,所以不在类外定义也可以
{
cout<<"mval="<<mval<<endl;
}
};
int Test::mval=4; //静态成员变量存在静态存储区中,所以需要在类外定义
int main()
{
Test::print(); //通过类名直接访问静态成员函数,打印: mval=4
}
16.友元friend
示例:
#include "stdio.h"
class Test{
private:
static int n;
int x;
int y;
public:
Test(int x,int y)
{
this->x = x;
this->y = y;
}
friend void f_func(const Test& t); //声明Test的友元是f_func()函数
};
int Test::n = 3;
void f_func(const Test& t)
{
printf("t.x=%d\n",t.x);
printf("t.y=%d\n",t.y);
printf("t.n=%d\n",t.n); //访问私有静态成员变量
}
int main()
{
Test t1(1,2);
f_func(t1);
return 0;
}
17.operator操作符重载函数
使'+,-,*,/'等操作符拥有了重载能力,能够实现对象之间的操作,而不再单纯的普通变量之间的操作了.
示例如下,实现一个加法类:
class Add
{
double mval;
public:
explicit Add(double t=0)
{
mval = t;
}
Add& operator +(const Add& t) //实现相同类对象相加
{
this->mval += t.mval;
cout<<"operator +(const Add& t)"<<endl;
return *this; //返回该对象,表示可以重复使用
}
Add& operator +(int i) //实现int型对象相加
{
this->mval += i;
cout<<"operator +(int i)"<<endl;
return *this;
}
Add& operator +(double d) //实现double型对象相加
{
this->mval += d;
cout<<"operator +(double d)"<<endl;
return *this;
}
Add& operator = (const Add& t) //重载赋值操作符
{
cout<<"operator =(const Add& t)"<<endl;
if(this!=&t)
{
mval = t.mval;
}
return *this;
}
double val()
{
return mval;
}
};
int main()
{
Add a1(11.5);
Add a2(1.25);
a1=a1+a2; //相当于调用两步: a1.operator =(a1.operator +(a2));
cout<< a1.val() <<endl;
}
运行打印:
18.通过()操作符重载实现:函数对象
示例:
class Test{
public:
void operator () (void) //通过()重载操作符,来使对象具备函数的行为
{
cout<<"hello"<<endl;
}
};
int main()
{
Test t;
t(); //来调用t这个函数对象打印"hello"
}
PS:好处在于可以封装自己的成员以及其它函数,所以能够更好的面向对象.
19.操作符重载实现:类型转换函数 示例如下:
class Test{
int mValue;
public:
Test(int i=0)
{
mValue=i;
}
operator int() //重载int类型
{
return mValue;
}
};
int main()
{
Test t(1000);
int i=t; //等价于: i=t.operator int();
cout<<i<<endl; //i=1000
}
20.explicit显式调用(用来阻止隐式转换) 示例:
class Test{
public:
explicit Test(unsigned int i)
{
cout<<"unsigned i= "<<i<<endl;
}
};
int main()
{
short num=3;
//Test t1=num; //Error,因为explicit阻止short类型 转换为unsigned int 类型
/*只能有以下3个方法实现*/
Test t2=(Test)num; //C方式强制转换,不推荐
Test t3=static_cast<Test>(num); //C++方式强制转换
Test t4(num); //手工调用构造函数
return 0;
}
21.父类和子类中的同名成员/函数
示例1-通过子类访问父类同名函数和同名成员:
class Parent{
public:
int mval;
Parent():mval(100)
{ }
void print()
{
cout<<"Parent: mval="<<mval<<endl;
}
};
class Child :public Parent
{
public:
int mval;
Child():mval(20)
{ }
void print()
{
cout<<"Child: mval="<<mval<<endl;
}
};
int main()
{
Child c;
c.Parent::print(); //调用父类的同名成员函数
cout<<c.mval<<endl;
cout<<c.Parent::mval<<endl; //打印父类的同名成员变量
}
22.子类对象初始化父类对象 以上示例的Parent类和Child类为例,在编译器中,可以将子类对象退化为父类对象,从而实现子类来初始化父类,比如:
Parent p1(Child()); //Child()构造函数会返回一个临时对象,从而通过子类初始化父类
Child c;
Parent & p2 = c ; //定义p2是C对象的别名
23.父类对象初始化子类对象 只能使用static_cast或者C方式转换,以上示例的Parent类和Child类为例:
Parent p;
Child *c = static_cast<Child *>(&p);
24.纯虚函数vertual
一个典型的示例,如下所示:
class Base //父类
{
public:
virtual void func() //声明func为虚函数
{
cout<<"Base: func()"<<endl;
}
};
class BaseA : public Base //子类A
{
public:
void func()
{
cout<<"BaseA: funcA()"<<endl;
}
};
class BaseB : public Base //子类B
{
public:
void func()
{
cout<<"BaseB: funcB()"<<endl;
}
};
void print(class Base& b)
{
b.func();
}
int main()
{
BaseA bA;
BaseB bB;
print(bA);
print(bB);
return 0;
}
运行打印:
如上图可以看到,我们以print(bA)为例: 再调用print()函数时,会将BaseA bA转换为父类Base,由于父类Base有个func()虚函数,所以会被动态替换为bA子类的func()函数.所以会打印funcA()
如果将上面代码virtual void func()改为void func()重新编译运行后,打印:
如上图可以看到,没有虚函数后,整个代码都变得没有灵活性,不适合类的扩展.
PS:在QT中,virtual用的非常多,比如QWidget的showEvent函数:
virtual void showEvent ( QShowEvent * event );
假如我们需要在窗口显示时加点特效时,只需要重写它即可,而QT库只需要根据vertual特性来自动调用我们重写的函数,非常灵活.
25.泛型函数模板(兼容不同类型)
函数模板是C++中重要的代码复用方式, 可通过不同类型进行调用
示例:
template <typename T> //声明使用模板,并定义T是一个模板类型
void Swap(T& a, T& b) //紧接着使用T
{
T c = a;
a = b;
b = c;
}
int main()
{
int a=0;
int b=1;
Swap(a,b); //自动调用,编译器根据a和b的类型来推导
float c=0;
float d=1;
Swap<float>(c,d); //显示调用,指定T是float类型
}
为什么函数模板能够执行不同的类型参数? 答:
函数模板也支持多参数,示例如下(如果定义了返回值模板,则必须要显示指定返回值类型,因为编译器不知道到底返回什么类型):
#include <iostream>
using namespace std;
template<typename T1,typename T2,typename T3>
T1 Add(T2 a,T3 b)
{
return static_cast<T1>(a+b);
}
int main()
{
// int a = add(1,1.5); //该行编译出错,没有指定返回值类型
int a = Add<int>(1,1.5); //指定T1为int形
cout<<a<<endl; //打印2
float b = Add<float,int,float>(1,1.5); //指定T1,T2,T3类型
cout<<b<<endl; //2.5
return 0;
}
26.泛型类模板(兼容不同类型) 类模板和函数模板一样,都是进行2次编译,需要注意的是定义对象必须显示指定所有类型 示例:
template<typename t1,typename t2,typename t3>
class Operator{
public:
t1 add(t2 num1,t3 num2)
{
return num1+num2;
}
};
int main()
{
Operator<float,int,float>t;
cout<<t.add(11,11.5)<<endl; //11+11.5 = 22.5
return 0;
}
27.数值型函数模板和数值型类模板(兼容不同数值) 数值型和泛型类似,但是数值型模板必须在编译时被唯一确定
示例1-数值型函数模板:
template <typename T,int N > //定义一个泛型值T,还有个int型的数值
void func()
{
T arr[N]; //使用模板参数T和N定义局部数组
}
int main()
{
func<int,10>(); //相当于实现 int arr[10]
}
示例2-数值型类模板(实现1+2+3+....+N值):
template < int N >
class Sum
{
public:
static const int VALUE = Sum<N-1>::VALUE + N; //通过Sum<N-1>::VALUE实现递归调用,并返回该临时对象
};
template < > //完全特化,因为我们知道N为1,所以不需要写< int N >
class Sum < 1 > //重载Sum类(类似于函数重载),当N==1时调用该类
{
public:
static const int VALUE = 1;
};
int main()
{
cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
return 0;
}
28.C++智能指针 头文件<memory> 1)auto_ptr
示例如下:
#include <iostream>
#include <memory>
using namespace std;
class Test{
int mvalue;
public:
Test(int i=0)
{
mvalue = i ;
cout<< "Test("<<mvalue<<")"<<endl;
}
~Test()
{
cout<< "~Test("<<mvalue<<")"<<endl;
}
};
int main()
{
auto_ptr<Test> p1(new Test(1));
auto_ptr<Test> p2(new Test(2));
cout<<"p1: addr="<<p1.get()<<endl;
cout<<"p2: addr="<<p2.get()<<endl;
p2 = p1;
cout<<"p1: addr="<<p1.get()<<endl;
cout<<"p2: addr="<<p2.get()<<endl;
return 0;
}
运行打印:
如上图所示,当我们执行p2=p1后,便执行了p2的析构函数进行自动释放了.并且p1.get()=0,所以auto_ptr具备自动释放功能以及同块堆空间下只能有一个指针对象特性
2) shared_ptr (需要C++11支持)
示例如下所示:
class Test{
public:
int mvalue;
Test(int i=0)
{
mvalue = i ;
cout<< "Test("<<mvalue<<")"<<endl;
}
~Test()
{
cout<< "~Test("<<mvalue<<")"<<endl;
}
};
int main()
{
shared_ptr<Test> p1(new Test(1));
shared_ptr<Test> p2(new Test(2));
cout<<"p1: addr="<<p1.get()<<endl;
cout<<"p2: addr="<<p2.get()<<endl;
p1.swap(p2); //互换p1和p2指针指向的地址
cout<<"p1: addr="<<p1.get()<<endl;
cout<<"p2: addr="<<p2.get()<<endl;
p1 = p2; //使p1指向p2指向的地址,并且释放p1之前指向的地址
cout<<"p1:addr="<<p1.get()<<", p2:addr="<<p2.get()<<endl;
cout<<"p1: unique="<<p1.unique()<<endl; //p1和p2指向同一片内存,所以为0
cout<<"p1: count="<<p1.use_count()<<endl;
return 0;
}
运行打印:
29.Qt中的智能指针 -QPointer
-QSharedPointer
-QScopedPointer
示例:
QScopedPointer<QPushButton> p1(new QPushButton);