创建对象,对象的内部结构最好使用结构化编程技术来架构(面向对象的架构和面向结构的方法和属性)
KIS(keep it simle)
研究程序编译时出现的每一条错误和警告信息
Tips:每个程序都应该以注释开头,以描述程序的用途
Namespace命名空间相当于作用域的限定,为了防止全局变量和自定义的变量名相同,而导致编译错误,未命名的namespace中的成员变量相当于全局变量,其余需要通过::访问该命名空间下的成员变量
Tips:避免使用_开头的标识符,以为这样的标识符容易与内部编译器使用的标识符冲突
在函数的起始处防止声明时,最好在所有声明结束后放上一个空行,因为这样可以更方便的区分声明以及可执行的语句。
运行结果
在一个if/else语句中,应该首先检测最有可能成为true的语句,也就是if的判断语句中一般为出现概率最大的事件。这样可以优化代码的运行速度。
Tips:浮点数都是约数,所以不能比较两个浮点数的大小,只能用两个浮点数的差值的绝对值是否小于一个指定的小值进行判断。
#include “cmath”数学库中包含常用的函数,如ceil(x),cos(x),ln(x),lg10(x),tan(x)等,使用时注意包含头文件。
函数的声明只是为了让编译器知道函数的参数类型,返回值类型,而于声明中具体的值无关,因此声明时可以只表明类型(如int fun(int)),而没有必要声明为int fun(int a);尽管不会错,但是尽量不要使传递给函数的形参和函数定义中的实参同名,这样做是为了避免歧义!
Tips:在任何情况下,一个函数的定义不能嵌套在另一个函数内,如将函数的定义放在main()函数中将出现编译错误!
以.h结尾的头文件是旧式的头文件,它们已经被C++标准库头文件代替,如下表
【注】cstdlib是个重要的标准库头文件,在随机数操作时需要用到~
rand()可以生成0~32767之间的随机数,要想生成小范围的随机数,可以采用取余运算。但是直接使用rand(),得到的随机数总是相同的,也就是说rand()生成的其实是“伪随机数”。解决方法:采用srand(seed),然后再调用rand()函数即可根据seed种子的不同,产生真正的随机数。
常用做法是使用time(0)来获取当前时间的秒数,然后调用rand(),但是注意time函数的原型包含在#include “ctime”中
利用static关键字定义的变量会在程序退出时保留当前值,在下一次运行函数时,仍可使用上一次退出时的值~
// randtest.cpp : 定义控制台应用程序的入口点。 //此函数主要用于验证static局部变量的全局效果 int say() { static int b=0; b=b+1; return b; } int main() { int a; cout<<say()<<endl;//第一次调用,返回1 cout<<say()<<endl;//再一次调用时,在原来的基础上加1 cin>>a; return 0; }
运行结果为:
用extern关键字定义的变量,可以在本源文件中使用外部定义的变量,但是外部的变量一般应为全局变量。当然也可以用extern声明函数。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// randtest.cpp : 定义控制台应用程序的入口点。//此函数主要用于验证extern变量的使用效果
int main()
{
int a;
extern int b;//标明b是外部声明的变量,编译器会在同文件下查找声明的全局变量b
cout<<b<<endl;//输出为5
cin>>a;
return 0;
}
// test.cpp : 定义控制台应用程序的入口点。
//此函数和上面的randtest.cpp属于同一个工程文件
#include "stdafx.h"
int b=5;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// randtest.cpp : 定义控制台应用程序的入口点。
//此函数主要用于验证static局部变量的全局效果
int main()
{
int a;
extern int b(int);//标明b是外部声明的函数,编译器会在同文件下查找同样定义的函数
cout<<b(5)<<endl;
cin>>a;
return 0;
}
// test.cpp : 定义控制台应用程序的入口点。
//此函数和上面的randtest.cpp属于同一个工程文件
#include "stdafx.h"
int b(int a)
{
return a;
}
另外register声明的变量是常用的数据,这些变量和我们常用的auto变量相比,不是放在内存中,而是放在寄存器中,因此对于常用数据来说,减少了取来取去的开销!
按值传递参数,只是提供参数的副本给函数,因此无论函数进行了怎样的处理,原参数值不会发生改变。
按引用传递参数,可以避免大量的数据被复制这种情况,但是安全性较差,因为数据容易被修改。
编译器只能通过参数列表来区分同名函数,因此重载函数的参数列表不能相同;而参数类表相同,返回值不同的同名函数是语法错误!
重载函数执行的是类似运算,这些运算会涉及到应用于不同数据类型的不同的逻辑运算。如果程序的逻辑运算对于各个数据类型都是一样的,那么利用函数模板可以更简洁的执行!
模板的定义是以关键字template<class T>或者template<typename T>开头
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// randtest.cpp : 定义控制台应用程序的入口点。
//此函数主要用于对比按值传递参数和按引用传递参数的区别
int funbyvalue(int a)
{
a=a*2;//其实是a的副本在进行乘法运算
return a;
}
int funbyref(int& a)//注意:不能重载,因为二者参数实际上相同的,都是int型
{
a=a*2;
return a;
}
int main()
{
int a=2;
cout<<"before by value"<<a<<endl;
cout<<funbyvalue(a)<<endl;;
cout<<"after by value"<<a<<endl;
cout<<"\n";
cout<<"before by ref"<<a<<endl;
cout<<funbyref(a)<<endl;;
cout<<"after by ref"<<a<<endl;
cin>>a;
return 0;
}
//此函数主要用于介绍一元作用域分辨符
int b=1;
int main()
{
int a;
int b=0;
cout<<b<<endl;//输出0
cout<<::b<<endl;//在有局部变量的情况下访问全局变量,输出1
cin>>a;
return 0;
}
编译器只能通过参数列表来区分同名函数,因此重载函数的参数列表不能相同;而参数类表相同,返回值不同的同名函数是语法错误!
重载函数执行的是类似运算,这些运算会涉及到应用于不同数据类型的不同的逻辑运算。如果程序的逻辑运算对于各个数据类型都是一样的,那么利用函数模板可以更简洁的执行!
模板的定义是以关键字template<class T>或者template<typename T>开头
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// templatest.cpp : 定义控制台应用程序的入口点。
//此程序主要用来测试template的用法,包括返回类型和参数类型不同,包括不同类型参数
template <class M,class T>
M fun(T value)
{
return value*9.9;
}
template <class M,class T>
T fun0(M value1,T value2)
{
return (T)(value1+value2);
}
int main()
{
int a;
cout<<fun<double>('A')<<endl;//注意<double>的位置,在函数名之后,参数之前
cout<<fun<double,int>('A')<<endl;//结果同上!标明template中的参数类型为<double,int>
cout<<fun0(1,2.5)<<endl;
cout<<fun0(2.5,1);
cin>>a;
return 0;
}
===========================================================================================================
Tips:质数的判断
若n(n∈N)不能被小于等于根号n的任一整数整除,则n为质数。
证明:(反证法)假设n不是质数,则n可以被分解为两个整数的乘积,即n=a*b,因此a,b中至少一个是小于等于n平方根,这与题设“n不能被小于等于根号n的任一整数整除”相矛盾,因此在判断质数和素数的时候,只要将for循环的上限设置为sqrt(n)即可。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//源于byrBBS,随机数不随机
#include<iostream>
#include<ctime>
using namespace std;
enum color{red,yellow,blue,white,black};
void main()
{
srand((unsigned)time(NULL));
int count1=0,count2=0,count3=0,count4=0,count5=0,count0=0;
for(int i=0;i<100;i++)
{ if(rand()*5/RAND_MAX==red)
count1++;
else if(rand()*5/RAND_MAX==yellow)
count2++;
else if(rand()*5/RAND_MAX==blue)
count3++;
else if(rand()*5/RAND_MAX==white)
count4++;
else if(rand()*5/RAND_MAX==black)
count5++;
else
count0++;
}
cout<<count1<<endl
<<count2<<endl
<<count3<<endl
<<count4<<endl
<<count5<<endl
<<count0<<endl;
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include "stdafx.h"
#include<iostream>
#include<ctime>
using namespace std;
enum color{red,yellow,blue,white,black};
void main()
{
int a;
srand((unsigned)time(NULL));
int count1=0,count2=0,count3=0,count4=0,count5=0,count0=0;
for(int i=0;i<100;i++)
{ int k=rand()%5;
if(k==red)
count1++;
else if(k==yellow)
count2++;
else if(k==blue)
count3++;
else if(k==white)
count4++;
else if(k==black)
count5++;
else
count0++;
}
cout<<count1<<endl
<<count2<<endl
<<count3<<endl
<<count4<<endl
<<count5<<endl
<<count0<<endl;
cin>>a;
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include "stdafx.h"
#include <iostream>
#include <ctime>
using namespace std;
enum color{red,yellow,blue,white,black};
void main()
{
srand((unsigned int)time(0));
int count1=0,count2=0,count3=0,count4=0,count5=0,count0=0;
for(int i=0;i<10000;i++)
{
switch(rand()%5)
{
case red:
count1++;
break;
case yellow:
count2++;
break;
case blue:
count3++;
break;
case white:
count4++;
break;
case black:
count5++;
break;
default:
count0++;
}
}
cout<<count1<<endl
<<count2<<endl
<<count3<<endl
<<count4<<endl
<<count5<<endl
<<count0<<endl;
}
数组的声明时为了让编译器知道应该为该数组预留多大的内存空间(类型占得字节*元素数),因此数组声明时需要标明元素的个数,否则编译器无从得知预留空间的大小。
将数组作为参数传递给函数时,不需要指明数组的大小,即便有大小,编译器也会将其忽略。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//本程序主要用于说明static数组变量的用法
void c(static char b[])//没有标明大小
{
for(int i=0;b[i]!=0;i++)
b[i]=b[i]+1;
cout<<b<<endl;
}
void main()
{
int a;
char d[]="really?";
c(d);
cout<<d;
cin>>a;
}
使用指针时,首先考虑指针的初始化工作,否则指针可能指向某些重要的内存单元,并进行修改!!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
// ptrbyref.cpp : 定义控制台应用程序的入口点。
//此程序主要用于演示指针变量按引用传递以及参数的设置问题
int ptrref(int *value)//参数是value,一指向整型的指针变量,而不是*value
{
return (*value)*(*value);
}
void ptrref(char *value)
{
for(;(*value)!=0;value++)
{
if((*value)>='A'&&(*value)<='Z')
*value=(*value)+32;
cout<<(*value);
}
}
int main()
{
int a;
int num=10;
cout<<ptrref(&num)<<endl;//参数需要时指针变量,是地址,因此要取常量的地址,即&
char str[]="HELLO";
ptrref(str);//str是数组的首地址,因此直接使用,不用&
cin>>a;
return 0;
}
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
int c(double *ptr)
{
return(sizeof(ptr));
}
void main()
{
int a;
double d[]={1.0,2};
double *ptr1=d;
cout<<c(ptr1)<<endl;//返回指针长度,不管指向什么类型的变量,均为4,说明指针占用4个字节
cout<<sizeof(d);//返回数组的长度,float占8个字节,所以d占2*8=16个字节
cin>>a;
}
指针进行加减运算的时候,并不是单纯的增加,而是增加其指向对象长度的倍数。
void main()
{
int a;
int *ptr;
cout<<ptr<<endl;//输出原地址785BB6F0
ptr++;//并不是单纯的加1,而是增加其指向的整型元素所占的4字节*1=4
cout<<ptr;
cin>>a;
}
一般的指针只有类型相同时,才能进行赋值运算,但是(void*)是个特例,该指针是个一般性指针,可以表示任何类型的指针,因此所有指针类型都能不经过转换就可以赋值给void指针,但是反过来,void指针要赋值给其他类型的指针,需要进行强制类型转换!
void main()
{
int a;
char *ptr[2]={"hello","world"};
for(;(*ptr[0]!=0);ptr[0]++)
{
cout<<(*ptr[0]);//输出hello
}
cin>>a;
}
ptr[2]是一个指向char型变量的指针数组,其中ptr[0]指向hello的首字母“h”,ptr[1]指向world的首字母“w”
1.字符串的输入可以不再采用前面常用的循环操作了,C++中定义了函数getline
函数原型为:cin.getline(char[],int len,’\n’)
3个参数分别存放字符数组,长度,以及分隔符(读到分隔符时自动忽略)
例子:char sentence[10];
Cin.getline(sentence,10,’\n’);
2.cout.put只能输出一个字符,而不是字符串,想连续输出,需要一直put,cout.put().put()
等价的,cin.get()也只能读入一个字符,并且将该字符作为返回值。
cin.get(char[],int len,’\n’)和cin.getline大致相同,唯一不同的是对分隔符的处理,get读取分隔符后放到字符串数组中,而getline读取后删除之
3.cin.read(buffer,size),cout.write(buffer,size)专用来处理无格式的输入输出,所有的数据均以原始的形式输入和输出!另外cin.gcount()可以统计最后一次输入操作读取的字符数
4.操作符<<重载:除了作为一般的输出流操作运算符外,<<还被重载用来打印以空字符结束的char*型数据,所以,要想取得字符串数组的首地址,必须将指针类型强制转换为void*类型。static_cast<void *>()
所以同getline()一样,可以不采用循环输出字符串,而用重载的运算符<<直接输出即可!
char ptr[]="sentence";
cout<<ptr<<endl;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void main()
{
int a;
char *ptr="sentence";
cout<<(*ptr)<<endl;//ptr指向字符串的首字母
cout<<ptr<<endl;//运算符<<被重载,不再输出首地址
cout<<static_cast<void *>(ptr)<<endl;//强制类型转换后取首地址
//拿普通数组和字符串数组进行比较
int b[]={1,2};
int *ptr1=b;
cout<<static_cast<void *>(ptr1)<<endl;
cout<<b;
cin>>a;
}
字符串处理库中的字符串操作函数
使用以下函数时,务必包含#include <cstring”>
Notice:结构体或者类的定义结尾处不加分号是个语法错误~所以自定义类的时候一定要慎重!
Notice2:构造函数不返回任何值和类型。
Notice3:数据成员通常是private,成员函数通常是public
Notice4:类的数据成员不能在类体的声明时初始化,一般通过构造函数初始化,也可以通过成员函数修改!
Notice5:同构造函数一样,析构函数也不带参数,不返回任何值,但是不同的是,析构函数不能重载!而且也没有必要重载,因为析构函数的目的是为了清理对象,与参数无关。
Notice6:成员函数可以在类的内部定义,这样此成员函数自动成为inline内联函数,但是这样客户就可以看到函数的实现方法,因此最好只在类的内部声明,在外部定义其实现!只有最简单的和最稳定的可以在类定义的内部实现。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Time
{
public:
Time(int,int,int);
Time();
void settime(int,int,int);
void gettime();
private:
int hour;
int min;
int sec;
};
Time::Time(int h,int m,int s)
{
hour=h;
min=m;
sec=s;
}
Time::Time()
{
hour=min=sec=0;
}
void Time::settime(int h,int m,int s)
{
hour=h;
min=m;
sec=s;
}
void Time::gettime()
{
cout<<hour<<":"<<min<<":"<<sec;
}
void main()
{
int a;
Time *newtime=new Time(10,0,0);
//Time newtime;
//Time newtime(10,0,0);
newtime->settime(13,05,30);
newtime->gettime();
cin>>a;
}
对象的声明方法和使用
[1] 使用指向对象的指针变量,注意new生成指针变量,因此newtime之前的*不能少!
Time *newtime=new Time(10,0,0);
newtime->settime(13,05,30);
newtime->gettime();
或者
Time *newtime=new Time();
newtime->settime(13,05,30);
newtime->gettime();
或者
Time *newtim;
newtime->settime(13,05,30);
newtime->gettime();
[2] 直接使用类的实例对象
Time newtime;
newtime.settime(13,05,30);
newtime.gettime();
或者
Time newtime(10,0,0);
newtime.settime(13,05,30);
newtime.gettime();
[3] 使用对象引用
Time newtime;
Time *timeptr=&newtime;
Time &timeref=newtime;
Tips:良好软件工程的基本原则之一就是将接口和实现方法分离,这样可以让程序更易于修改,同时改变类的实现方法对使用类的客户没有影响,只要提供给客户的类的原接口不便。
常用的做法:将类声明放在使用该类的任何客户的头文件中,形成类的public接口,将类成员函数的定义放在源文件中,形成类的实现方法。
例如在time.h中声明类Time,在time.cpp中定义类的成员函数(但是头文件需要包含time,h),在另一个cpp文件中执行main函数。这样即可实现类的接口和实现方法的分离!
在不知道其他文件是否包含头文件时,可以使用
#ifndef TIME_H
#define TIME_H
….
#endif
类数据为private并不代表客户不能修改这些数据,可以通过该类的成员函数和友元函数修改这类数据。
通过get或者set成员函数修改:但是类设计人员不需要给每个private数据成员设置set和get函数,只有在需要的时候才提供,如果服务对于客户代码是有用的,则应该在类的private接口中提供服务。
类的友元函数在类的作用域之外定义,但是有权访问类的private和protected成员
要在class2类声明为class1的友元,应该在class1中声明friend class2;
尽管类定义中有友元函数的原型,但是友元函数不是成员函数。同时友元关系的声明和private,protect,public的成员访问符号无关,因此友元关系声明可以放在类定义的任何位置。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Time
{
friend void sethour(Time&,int);//必须按引用传递,否则不能更改Time类的成员变量的值
public:
Time();
int gethour();
private:
int hour;
};
Time::Time()
{
hour=0;
}
void sethour(Time &t,int value)//void Time::sethour(Time &t,int value)出错,因为虽然是类的友//元函数,但是定义域不在类中
{
t.hour=value;
}
int Time::gethour()
{
return hour;
}
void main()
{
int a;
Time newtime;
cout<<"before the sethour function "<<newtime.gethour()<<endl;
sethour(newtime,10);
cout<<"after the sethour function "<<newtime.gethour();
cin>>a;
}
【注】采用按引用传递 类的友元函数不是成员函数,是独立的函数!所以可以类中放在任意位置
合成:对象作为另一个类的成员,是软件重用的最常见的形式。
静态类成员看上去像全局变量,但是static数据成员有类作用域,静态成员可以使public,private,protected。静态成员在文件范围内必须一次初始化,类的public静态成员可以通过类的对象访问,也可以通过类名和二元作用域分辨符访问,而类的protected和private静态成员必须通过类的静态成员访问。尽管静态成员声明时需要加关键字static,但是定义的时候不能加,否则是语法错误!与非静态成员函数相比,静态成员函数没有this指针,因为this指针时依赖于对象存在的,而没有对象,也可以有静态成员。
继承
继承对于软件的可重用性是个重要方面。创建新类时,不需要全部重写代码,只需要指明“新类”所要“继承”的已定义“基类”的数据成员和成员函数,同时增加自己的数据成员和函数成员。每个派生类还可以继续派生新类。在多重继承中,一个类可以从多个基类中派生。
Public继承:派生类的每个对象都可以认为是基类的对象。基类中的public和protected成员分别被继承为派生类的public和protected成员,基类的private成员在派生类中是不能访问的,只能通过基类的public成员函数访问,另外友元函数也不能被继承。
Protected继承:基类的public和protected成员在派生类中均为protected,只有非静态成员函数和友元函数可以直接访问。
Private继承:基类的public和protected成员在派生类中均为Private,只有非静态成员函数和友元函数可以直接访问。
Egs:继承以及成员函数的改写!
注意:改写基类的成员函数时,派生类中也要声明和基类一样结构的成员函数。同时改写的过程中需要使用基类的类名和::已调用基类的成员函数,否则会是自己循环调用,语法错误
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Time
{
friend void sethour(Time&,int);//必须按引用传递,否则不能更改Time类的成员变量的值
public:
Time();
int gethour();
private:
int hour;
};
Time::Time()
{hour=0;}
void sethour(Time &t,int value)//void Time::sethour(Time &t,int value)出错,因为虽然是类的友//元函数,但是定义域不在类中
{t.hour=value;}
int Time::gethour()
{return hour;}
class Day:public Time
{
public:
Day();
int getyear();
int gethour();//需要先声明此成员函数,才能改写!
private:
int year;
};
Day::Day()
{year=2010;}
int Day::getyear()
{return year;}
int Day::gethour()
{return Time::gethour()+12;//改写Time基类的gethour()函数}
void main()
{
int a;
Day d;
Time t;
cout<<t.gethour()<<endl;
cout<<d.gethour();
cin>>a;
}
派生类生成对象的时候,需要先调用基类的构造函数,以初始化基类的成员函数(初始化后的数据成员,只有派生类能看到,显示的调用基类的对象察看数据成员时没有变化),然后调用派生类的构造函数。若不是显示在派生类的构造函数中添加基类的构造函数,则调用默认的构造函数
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Time
{
public:
Time(int val);
int gethour();
private:
int hour;
};
Time::Time(int val)
{
hour=val;
cout<<"Time Construction"<<hour<<endl;
}
int Time::gethour()
{
return hour;
}
class Day:public Time
{
public:
Day(int value);
int getyear();
private:
int year;
};
Day::Day(int value):Time(value)//初始化基类和派生类的构造函数
{
year=value;
cout<<"Day Constrction"<<year<<endl;
}
int Day::getyear()
{
return year;
}
void main()
{
int a;
Time t(5);
cout<<t.gethour()<<endl;
Day d(10);
cout<<t.gethour()<<endl;
cout<<d.gethour();
cin>>a;
}
程序的重点在黑色加粗部分,在初始化派生类的构造函数时,也显式的调用基类的构造函数。假如去掉:Time(value),程序会提示错误,原因在于程序无法找到默认的无参数构造函数。
虚拟函数
假设有一组形状类如Circle,Triangle,Rectangle等等都是从基类Shape继承而来,在面向对象编程中,类似的每个类可能都要绘制自身的形状,即使每个类都要有自己的draw函数,但是每个类的draw函数不尽相同,在绘制每一个形状时,无论要绘制什么形状,最好都将所有的形状都作为基类Shape的对象来处理,这样一来,无论绘制什么形状,所要做的就是调用基类Shape的draw函数,让程序动态确定(运行时确定)执行相应的派生类的draw函数。为了实现这一目的,我们将基类中的draw声明为虚拟函数,然后每个派生类中改写draw,使之绘制出自己想要的形状。
虚拟函数的声明:函数的原型前加关键字virtual。如virtual void draw()const;
多态性:通过继承相关的不同的类,对象能够对同一个函数做出不同的响应。多态性是通过虚拟函数实现的。通过指针传递或者引用传递继承类的对象告诉基类要动态的绑定哪个virtual函数。
Egs:本程序主要说明多态性是如何通过指针或者引用传递参数实现动态绑定的!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
class Employee
{
public:
virtual void print();//关键字virtual很重要!
};
void Employee::print()
{
cout<<"Employee:";
}
class Boss:public Employee
{
public:
virtual void print();
};
void Boss::print()
{
cout<<"Boss"<<endl;
}
class Manager:public Employee
{
public:
virtual void print();
};
void Manager::print()
{
cout<<"Manager"<<endl;
}
void viaptr(Employee *e)//通过指针传递参数实现多态性的绑定
{
e->print();
}
void main()
{
int a;
Boss newboss;
viaptr(&newboss);
Manager newm;
viaptr(&newm);
cin>>a;
}
函数重载通常是对不同的数据类型执行相似的操作,如果是对各种不同的数据类型执行相同的操作,函数模板将更加方便。程序员只编写一次函数模板的定义,根据调用函数时提供的参数类型,编译器会产生相应的目标代码函数以正确的处理各种类型的调用。模板函数和重载密切相关,通过模板产生的相关函数均同名,因此编译器会用重载的方法调用相关的函数。
P6实例(单击察看)---模板的定义和重载
尽管模板只编写一次,但是程序中仍需实例化多个模板和模板类的副本,这些副本会占用大量内存。
模板类:同模板函数类似,只需要编写一个通用类模板的定义,在需要用模板建立一个新类时,只需要一种简洁的表达方式,让编译器写出程序需要的模板类源代码。
注意:模板类的类型可以看做class 类名<typename T>,就是在原来的基础上增加类型参数typename。成员函数的定义需要以template <class T>开头
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//模板的定义中类的声明
template <class T>
class shape
{
public:
int draw(T);
private:
T length;
};
//类成员函数的定义。注意:模板函数的类型为class 类名<typename T>,
//如下的class shape< typenameT>,所有的成员函数定义都要如此!
template <class T>
int shape<T>::draw(T value)
{
length=value;
return (int)length;
}
int main()
{
int a;
int val=8;
shape<int> newshape;//函数的声明方式
cout<<newshape.draw(val)<<endl;
shape<float> shape1;
float valu=11.5;
cout<<shape1.draw(valu);
cin>>a;
return 0;
}
为了提高程序的可重用性和可维护性,我们用类,类模板,继承和构造来创建包装链表,堆栈,队列等数据结构。
自引用类:包含一个指针成员,它指向与其具有相同类型的对象。
class Node{
public:
Node(int);
void setNextptr(Node *);
private:
int data;
Node *nextptr;//nextptr同样指向的同样是Node类的一个对象。
};
没有释放不再需要的动态分配内存会导致系统过早耗尽内存,就是所谓的“内存泄露”因此不再需要由操作符new分配的内存时,立刻由操作符delete释放。
链表与数组相比有个明显的优势:链表是动态的,可以随着需要增加或者减少,但是数组的大小是固定的,因为其大小需要在编译的时候就确定,所以常规的数组很容易因为数组剧增而溢出,链表只有在内存不足的时候才会变满。另外数组的插入和删除很好时间,因为插入点或者删除点之后的元素要全部移动,但是链表执行这样的操作变的很简单,只要更改指针的指向即可,原有的数据无需移动!
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
template <class nodetype> class list;//声明
//结点类声明,定义省略
teplate <class nodetype>
class listnode
{
friend class list<nodetype>;
public:
listnode(nodetype&);
nodetype getdata();
private:
nodetype data;
listnode<nodetype> *nextptr;
};
//链表类的声明,定义省略
template <class nodetype>
class list
{
public:
list();
~list();
void insertatfront(nodetype&);
void insertatback(nodetypr&);
void removefromfront(nodetype&);
void removefromback(nodetype&);
bool isempty();
void print();
private:
listnode<nodetype> *firstptr;
listnode<nodetype> *lastptr;
listnode<nodetype> *getnewnode(nodetype&)
};
堆栈类模板,只需要调用上面链表类的成员函数即可。
方法1:通过继承list模板类
template <class stacktype> class stack:private list<stacktype>//继承list模板类 { public: void push(stacktype &s) { s.insertatfront(s); } void pop(stacktype &s) { s.removefromfront(s); } bool isstackempty() { return isempty(); } void printstack() { print(); } };
方法2:通过合成的方法
template <class stacktype> class stack { public: void push(stacktype &s) { d.insertatfront(s); } void pop(stacktype &s) { d.removefromfront(s); } bool isstackempty() { return d.isempty(); } void printstack() { d.print(); } private: list<stacktype> d;//list模板类的成员变量 };
队列类模板,同堆栈类模板雷同,唯一不同的只是调用list类模板中的成员函数不同而已。主要原因是:堆栈先进后出,而队列先进先出。
方法1:通过继承list模板类
template <class queuetype> class queue:private list<stacktype>//继承list模板类 { public: void enqueue(queuetype &s) { s.insertatback(s); } void dequeue(queuetype &s) { s.removefromfront(s); } bool isqueueempty() { return isempty(); } void printqueue() { print(); } };
方法2:通过合成的方法
template <class queuetype> class queue { public: void enqueue(queuetype &s) { d.insertatback(s); } void pop(queuetype &s) { d.removefromfront(s); } bool isqueueempty() { return d.isempty(); } void printqueue() { d.print(); } private: list<queuetype> d;//list模板类的成员变量 };
字符串的使用需要包含#include <string>
其实string只是个别名,Typedef basic_string<char> string.
初始化:string s(“hello”)或者string s=”hello”;记住这里的操作符=不是赋值操作符,它只是调用string类的构造函数隐式的执行这种转换。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
void main()
{
int a;
string s(3,'s');//输出sss
s.append("out");//string追加运算
string s1;
s1.assign(s);//string附加运算
s1=s1+"world";
if(s>s1)
cout<<"s:"<<s<<" s1:"<<s1<<" s>s1"<<endl;
else
cout<<"s:"<<s<<" s1:"<<s1<<" s<=s1"<<endl;
if(s.compare(s1)>0)//if(s-s1>0)
cout<<"s>s1 via compare"<<endl;
else
cout<<"s<=s1 via compare"<<endl;
cout<<s.substr(3,2)<<endl;//子串从第个下表开始,读个字符
s.swap(s1);//字符串交换运算
cout<<s<<endl;
cout<<s.find("tw")<<endl;//返回出现tw字符串的下表
s+="tw";
cout<<s.find_first_of("tw")<<endl;
cout<<s.find_last_of("tw")<<endl;
s.insert(10,"hello");//将hello插入到s的第个字符位置
cout<<s<<endl;
cin>>a;
}