EssentialC++chap4(基于对象的编程风格)总结
类的介绍
- class
public公共接口
- 对象生命或定义
- 成员函数声明或定义
private私有成员
- 对象声明或定义
- 成员函数声明或定义
实现一个类,分文件式
- 类主体class{}放在头文件里
- 类成员函数的部分或全部定义放在头文件附属的程序代码文件里
- 主函数程序代码文件,可以调用类的公共接口(public)里的成员函数,但私有成员只能成员函数调用或者友元类里访问
类主体外定义成员函数,需要类名::成员函数名(参数表){}
- 类名 对象名;创建类对象
析构函数和构造函数
构造函数:用来初始化类里的私有成员
- 放在public里:
- 可以重载构造函数
- 成员初始化列表法定义类的构造函数
- 普通法定义类的构造函数(类主体外带::的像定义普通函数一样的定义方式)
- 创建类对象时后面附着()参表,就相当于定义类对象同时初始化类对象里的私有成员
- 需要配合析构函数,因为构造函数和析构函数定义内部产生出来的对象都在堆内存里。
析构函数:在类对象的生命周期结束后析构函数进行其资源(内存)的释放,需要手动定义
- 其定义可以放在类主体的公共接口里和构造函数定义一起出现
- ~构造函数名()没有参数,没有返回值。
-带有构造函数和析构函数的类对象被定义后,编译器自动调用构造函数为其初始化,程序执行到其生命周期结束编译器自动调用析构函数释放其所占的内存
mutable(可变)和const(不变)
mutable
- 用于修改一些辅助的变量如泛型指针(迭代器)但这不破坏类对象的内容。所以const缀在成员函数参表后,成员函数内部修改的不破坏类对象内容的私有成员在private:上写成mutable 类型名 对象名;
const
- 可以用于成员函数定义或声明上,在函数参表后缀一个const,代表这个成员函数不会更改类对象的内容
this指针
- 直接访问类私有成员的途径之一
- 在成员函数定义(内部)出现,指向调用这个成员函数的类对象
静态类成员
- 所有类对象共享这一个静态类成员,增删查改都方便(类比qq群文件资源)
- static开头的静态类成员对象声明放在类主体的private里,但是其定义(除了开头没有static外其他和声明相同的静态类成员对象)放在头文件附属的程序代码文件里,必须切记!
静态成员函数
- 不修改类对象内容的成员函数,但可以修改静态类成员对象
泛型指针类
- 类对象的运算不可以直接运算,需要运算符重载
运算符重载规则
-
不引入新运算符
-
运算符操作数不可改,几元运算符就是几元
-
运算符优先级不可改
-
重载运算符函数的参表中必须有一个是class型
-
重载运算符函数不需要函数名,就在重载的运算符前加这些内容—>类名::operator 要重载的原运算符(参表){}
嵌套类型
- typedef简化对象定义的类型名
友元函数,友元类
-
在某类的主体里把其他类的成员函数原型最头里加上个friend,就可以让其他类的成员函数访问某类的私有成员
-
在某类里设定其他类为friend,那就是在某类的主体内写friend class 其他类的类名;这样其他类的成员函数都是某类的friend,都可以访问某类的私有成员
-
为了效率,像是inline函数那样跳到某类里访问private成员,然后其他类的友元函数展开运行这样子。
-
某类使用的成员函数访问其他类的成员函数,某类的成员函数不需要和其他类建立友谊
拷贝赋值运算符
- 重载=运算符,以适应复制类对象(如tri1=tri2,tri1 tri2都是一个类的类对象)这种操作
函数对象
-
用于泛型算法,比如sort(),find_if()
-
首先定义函数对象所属的类。并重载()运算符,定义写类主体外(.cpp文件里),声明写class public里。
-然后定义这个函数对象的类的对象,重载的运算符()要是有参表把参照数放在类对象()(参表)里
重载iostream运算符
-
ostream&的返回类型打头用作重载<<运算符
-
istream&的返回类型打头用作重载>>运算符
-
实质上重载运算符的步骤和注意事项还是和重载其他运算符一样
-
重载iostream运算符要把重载运算符函数定义放在类主体外,否则需要挂着类对象写cout很麻烦,声明可以放类主体内。
类成员函数指针
-
灵活性增强。简化调用成员函数等工作,省写很多代码
-
二维vector,注意> >中间有空格(maximal munch编译规则)
-
定义类成员函数的指针时注意格式返回类型(类型名::*指针名)(参表)(=0);=0是让定义的指针成为空指针
-
.*运算符是成员选择运算符,搭配类对象用,(类对象.*类成员函数指针)(参表,可不写参数名)
-
->*运算符也是成员选择运算符,搭配类对象指针用,(类对象指针名->*类成员函数指针)(参表,可不写参数名)
本章习题
- 4.3把给的数据(类型名 对象名;)包装起来。
/** 考虑一下所定义的全局(global)数据:
*string _program_name;
*string _version_stamp;
*int _version_number;
*int _tests_run;
*int _tests_passed;
*编写一个用以包装这些数据的类
*/
#include<iostream>
#include<string>
//什么程序都得有#include<iostream>和using namespace std;否则编译器报错:
//error: 'string' does not name a type|
using namespace std;
class globalWrapper{
public:
static int tests_passed(){return _tests_passed;}
static int tests_run(){return _tests_run;}
static int version_number(){return _version_number;}
static string version_stamp(){return _version_stamp;}
static string program_name(){return _program_name;}
static void tests_passed(int nval){_tests_passed=nval;}
static void tests_run(int nval){_tests_run=nval;}
static void version_number(const int nval){_version_number=nval;}//这里参数类型变成const int型也可以
//也就是说const型变量或常量可以赋值给非const型变量!
static void version_stamp( const string& nstamp )
{ _version_stamp = nstamp; }
static void program_name( const string& npn )
{ _program_name = npn; }
private:
static string _program_name;
//声明globalWrapper类中有个static string类型的变量,
//static的话变量就变成公共的不属于任何类的实例了。
static string _version_stamp;
static int _version_number;
static int _tests_run;
static int _tests_passed;
//因为是全局数据,所以在类主体里私有成员和成员函数都定义成静态的。
};
string globalWrapper::_program_name;
//static string类型的变量的初始化(或说定义)(类主体外的该变量可以不写static)
string globalWrapper::_version_stamp;
int globalWrapper::_version_number;
int globalWrapper::_tests_run;
int globalWrapper::_tests_passed;
/** 程序执行顺序--if条件语句判断,进入类成员函数(重载函数static string返回类型)program_name(),因为_program_name成员变量是static型,
* 所以初值为0,因为program_name()会返回一个static string型变量,所以if()里变成了返回的static string型的_program_name.empty(),
* 检查该变量是否为空,检查确实为空,走if,然后进入类成员函数(重载函数static void返回类型)program_name(const string&),该函数接收一个实参,
* 把实参"ex_4_3"传入,经该语句_program_name = npn;让_program_name这个static string型变量被赋值为实参的值(ex_4_3)。退出函数。然后走到类
* 成员函数(重载函数static void返回类型)version_number(const int),经该语句_version_number=nval;让static int型变量_version_number被赋值为实参的
* 值1,退出该函数。再入类成员函数(重载函数staticvoid返回类型)version_stamp( const string&),经该语句_version_stamp = nstamp;让_version_stamp
* 这个static string型变量被赋值为实参的值"A1-OK",退出函数;走到下面的if,进入类成员函数(重载函数static string返回类型)program_name(),
* 返回一个static string型变量_program_name(值为"ex_4_3"),然后判断这个变量的值和"ex_4_3"等不等,一看相等,走if内,输出"Wrapper seems to
* work ok\n",然后走到主函数末尾},程序结束。
* 所以用到了函数重载,static型变量定义时自动赋值0,const和非const型传参及const-const传参,容器(string)泛型算法的这些知识。
*/
int main()
{
if( globalWrapper::program_name().empty() )
//name后面的()不可去掉,否则编译器会报错:error: 'globalWrapper::program_name' does not have class type|
{
globalWrapper::program_name( "ex_4_3" );//通过类成员函数给string型对象赋值。
globalWrapper::version_number( 1 );//通过类成员函数给int型对象赋值。
globalWrapper::version_stamp( "A1-OK" );//通过类成员函数给string型对象赋值。
}
if( globalWrapper::program_name() == "ex_4_3" )
cout << "Wrapper seems to work ok\n";//如果string型类对象的值是"ex_4_3",输出这条语句
else
cout << "Hmm. Wrapper doesn't seem to be correct\n";//不是的话输出这条语句
}
//如果if else for等一些条件语句循环语句没放在main函数内,编译器会报错
//error: expected unqualified-id before 'if'(或者'else'、'for'等)|
报错问题4.3
①什么程序都得有#include<iostream>
和using namespace std;否则编译器报错:
error: ‘string’ does not name a type|
②name后面的()不可去掉,否则编译器会报错:error: ‘globalWrapper::program_name’ does not have class type|
③如果if else for等一些条件语句循环语句没放在main函数内,编译器会报错
error: expected unqualified-id before ‘if’(或者’else’、'for’等)|
4.4
//最好保持头文件定义格式
#ifndef _USERPROFILE_H_
#define _USERPROFILE_H_
#include<map>
#include<iostream>
#include<string>
#include<cstdlib>
using namespace std;
class UserProfile{
public:
enum uLevel{Beginner,Intermediate,Advanced,Guru};
//定义枚举型变量uLevel代表用户的等级,这里没有指明枚举量的值,所以
//默认Beginner为0
UserProfile(string login,uLevel=Beginner);
UserProfile();//重载(两种)类构造函数的声明
//不需要析构函数,因为你没有从堆内存里找出空间给某对象(没用new等一些语句)
//不用拷贝构造函数和拷贝构造运算符,你不可能把一个用户(类对象)赋值给
//另一个用户(类对象),这没有意义。
bool operator==( const UserProfile& );
bool operator!=( const UserProfile &rhs );
// 下面是用来读取数据的函数,读取的数据作为返回值输出到屏幕上,配合<<重载运算符函数使用
//因函数短小,所以直接定义在类主体里,头上加个inline提升程序执行速度
inline string login()const{return _login;}//用户(类对象)登录记录读取函数
inline string user_name()const{return _user_name;}//用户(类对象)用户名读取函数
inline int login_count()const{return _times_logged;}//用户(类对象)登录次数读取函数
inline int guess_count()const{return _guesses;}//用户(类对象)猜测总次数读取函数
inline int guess_correct()const{return _correct_guesses;}
//error: expected identifier before 'return'| 报错原因:const后{写成了(导致大括号不匹配
//用户(类对象)猜对次数读取函数
//下面是计算用户的猜对百分比函数,返回double型(猜对百分比)变量,配合<<重载运算符函数使用
double guess_average()const;//比较复杂,在此先声明。定义在.cpp文件里
//下面是提供所有的用户等级(存在一个静态string容器里供UserProfile类所有类对象使用
string level()const;//比较复杂,在此先声明。定义在.cpp文件里
//下面是用来写入数据到用户(类对象)对应的数据成员的函数,接收一个从标准输入设备输入进来的参数。
//配合>>重载运算符函数使用
//隐函数短小,所以直接定义在类主体里。
inline void reset_login(const string &val){_login=val;}
//从标准输入设备读取登录记录,并赋值给用户(类对象)的登录记录数据成员
inline void user_name(const string &val){_user_name=val;}
//从标准输入设备读取用户名,并赋值给用户(类对象)的用户名数据成员
inline void reset_level(const string &);
//关于写入等级到用户等级的重载函数,
inline void reset_level(uLevel newlevel){_user_level=newlevel;}
//从标准输入设备读取等级,并赋值给用户(类对象)的用户等级数据成员,参表类型是枚举类型
inline void reset_login_count(int val){_times_logged=val;}
//从标准输入设备读取登录次数,并赋值给用户(类对象)的登录次数数据成员
inline void reset_guess_count(int val){_guesses=val;}
//从标准输入设备读取猜测次数,并赋值给用户(类对象)的猜测次数数据成员
inline void reset_guess_correct(int val){_correct_guesses=val;}
//从标准输入设备读取猜对次数,并赋值给用户(类对象)的猜对次数数据成员
inline void bump_login_count(int cnt=1){_times_logged+=cnt;}
//通过类对象调用成员函数,若传入参数,参数加在调用成员函数的用户(类对象)的登录次数上
//若未传入参数,默认用户的登录次数+1
inline void bump_guess_count(int cnt=1){_guesses+=cnt;}
//通过类对象调用成员函数,若传入参数,参数加在调用成员函数的用户(类对象)的猜测次数上
//若未传入参数,默认用户的猜测次数+1
inline void bump_guess_correct(int cnt=1){_correct_guesses+=cnt;}
//error: 'void UserProfile::bump_guess_count(int)' cannot be overloaded|
//报错原因:抄错了。。。。count应该是correct
//通过类对象调用成员函数,若传入参数,参数加在调用成员函数的用户(类对象)的猜对次数上
//若未传入参数,默认用户的猜对次数+1
private:
static string guest_login();//静态成员函数,不会访问任何非静态类成员
//作用是
string _login;
string _user_name;
int _times_logged;
int _guesses;
int _correct_guesses;
uLevel _user_level;
//用户的登录记录,用户名,登录次数,猜测次数,猜对次数,用户等级这些
//私有数据成员的定义。
// 给出警告,应该在这些数据成员(私有成员)在声明时一起初始化它。不过这无大碍
//warning: 'int UserProfile::_times_logged' [-Wreorder]|
//warning: when initialized here [-Wreorder]|
static map<string,uLevel> _level_map;
static void init_level_map();
//作用是:初始化map容器
};
inline UserProfile::UserProfile(string login,uLevel level)
:_login(login),_user_level(level),_times_logged(1),_guesses(0),_correct_guesses(0)
{}
//类构造函数(成员逐一初始化形式)的定义,函数原型是这个
//UserProfile(string login,uLevel=Beginner);
inline UserProfile::UserProfile()
:_login("guest"),_user_level(Beginner),_times_logged(1),_guesses(0),_correct_guesses(0)
{
static int id=0;
//error: stra_itoay '\243' in program|,错误原因代码里有非英文格式的标点符号
char buffer[16];
_itoa(id++,buffer,10);
_login+=buffer;
//_itoa()是C标准库提供的函数,把整数转换给对应的ASCII字符串形式。
//针对guset,加入一个独一无二的会话标识符(让用户登录记录独一无二),用来
//区分经上面那个默认构造函数初始化的类对象(区分类对象的登录记录),
}
//报错:undefined reference to `UserProfile::UserProfile()'|!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//原因:类构造函数的定义放在定义类主体的头文件里即可!!!
#endif // _USERPROFILE_H_
//UserProfile.cpp
#include"UserProfile.h"
inline double UserProfile::guess_average()const
{
return _guesses?double(_correct_guesses)/double(_guesses)*100:0.0;
//用户的猜对百分比计算函数(类成员函数,很短小,所以用inline)
//猜对的次数和猜测的次数是我们自己输入进用户(类对象)的数据成员_guesses和_correct_guesses的
//比如anna.bump_guess_count( 27 );和anna.bump_guess_correct( 25 );
//如果用户猜测次数为0,直接输出正确率是0.0)
//用到了强制类型转换。因为是/,不强制类型转换的话小数点后数据会丢弃
//因为算猜对百分比,所以要*100
}
//类构造函数(成员逐一初始化形式)的定义,函数原型是这个
//UserProfile();
inline bool UserProfile::operator==(const UserProfile &rhs)
{
if(_login==rhs._login && _user_name == rhs._user_name)
{
//报错error: no match for 'operator&&' (operand types are 'const string
//{aka const std::__cxx11::basic_string<char>}'
//and '__gnu_cxx::__enable_if<true, bool>::__type {aka bool}')|
//原因:==写成了=
return true;//检测两个用户的登录记录和用户名是否重复了,重复了返回true
}
else{return false;}
}
inline bool UserProfile::operator!=(const UserProfile &rhs)
{
return !(*this==rhs);//检查两个用户的数据成员重复问题。重复了返回false,不重复返回true
}
inline string UserProfile::level()const
{
static string _level_table[]=
{
"Beginner","Intermediate","Advanced","Guru"
};
return _level_table[_user_level];
}
map<string,UserProfile::uLevel>UserProfile::_level_map;
//定义查询用户等级的map容器。等级是string型,等级出现次数是枚举类型uLevel
void UserProfile::init_level_map()
//函数原型static void init_level_map();
{
_level_map["Beginner"]=Beginner;
_level_map["Intermediate"]=Intermediate;
_level_map[ "Advanced" ] = Advanced;
_level_map[ "Guru" ] = Guru;
//这个函数作用是把全部用户等级对应地存储在map里。
}
inline void UserProfile::reset_level(const string &level)
{
if(_level_map.empty())
{
//如果用户等级的map容器为空,走if,给map容器赋值
init_level_map();
}
map<string,uLevel>::iterator it;
_user_level=((it=_level_map.find(level))!=_level_map.end())?it->second:Beginner;
//根据运算符优先级结合性可拆分成it=_level_map.find(level);it!=_level_map.end();
//定义一个泛型指针(map<string,uLevel>型),it->second是指向了map的第二个参数即键值
//这个函数的作用是确保level(用户等级)的确是一个可识别的等级
//(需要输入用户等级给类对象的数据成员——用户等级),不可识别的等级全给我归为beginner!
}
ostream& operator<<( ostream &os, const UserProfile &rhs )
{ /* output of the form:
* stanl Beginner 12 100 10 10%
**/
os << rhs.login() << ' '
<< rhs.level() << ' '
<< rhs.login_count() << ' '
<< rhs.guess_count() << ' '
<< rhs.guess_correct() << ' '
<< rhs.guess_average() << endl;
//从上到下依次进入这些类成员函数(重载void类型),将存在类对象的数据成员(私有)输出
//到屏幕中
return os;
}//第一次登陆的游客(或说是测试的类对象会被输出guestN beginner 1 0 0 0
istream& operator>>( istream &is, UserProfile &rhs )
{ // yes, this assumes the input is valid ...
string login, level;
is >> login >> level;
int lcount, gcount, gcorrect;
is >> lcount >> gcount >> gcorrect;
rhs.reset_login( login );
rhs.reset_level( level );
rhs.reset_login_count( lcount );
rhs.reset_guess_count( gcount );
rhs.reset_guess_correct( gcorrect );
return is;
}
//main.cpp
#include<map>
#include<iostream>
#include<string>
//后面会用到,和map头文件
#include"UserProfile.h"
//>>和<<运算符已经移入.cpp文件夹。
//error: expected nested-name-specifier before 'namespace'|
//报错原因不详,但慎用using namespace std;因为
//任何情况下都不要using namespace std从理论上来说也是有道理的:
//因为系统库可能会升级,这样升级编译使用的C++版本的时候有可能因为
//引入了新的符号跟自己代码里的命名冲突。但一般来说,
//升级C++版本最多几年也就做一次,冲突的可能性也并不大,
//而升级C++版本本来也不一定能保证编译成功,
//为了这种特殊时候省一点时间让平时的编码和阅读都变费劲并没有什么道理。
//故不用了using namespace std;,改成每个cin cout前加std::
using namespace std;
ostream& operator<<( ostream &os, const UserProfile &rhs );
istream& operator>>( istream &is, UserProfile &rhs );
int main()
{
UserProfile anon;//在定义类对象的同时将类对象的数据成员(私有成员)部分初始化
//采用默认的成员逐一初始化方式(对应下面这个构造函数)
///inline UserProfile::UserProfile()
///: _login( "guest" ), _user_level( Beginner ),
/// _times_logged( 1 ), _guesses( 0 ), _correct_guesses( 0 )
/// {
/// static int id = 0;
/// char buffer[ 16 ];
/// _itoa( id++, buffer, 10 );
/// _login += buffer;
///}
cout<<anon;
//测试<<运算符所定义的测试类对象,调用<<运算符重载函数输出anon_too的数据
UserProfile anon_too;
cout<<anon_too;
//测试<<运算符所定义的测试类对象,调用<<运算符重载函数输出anon_too的数据
UserProfile anna("AnnaL",UserProfile::Guru);
//注意类主体里定义的用户等级(枚举量)
//所以要加类名::
//在定义类对象的同时将类对象的数据成员(私有成员)部分初始化(对应下面这个构造函数)
///inline UserProfile::UserProfile( string login, uLevel level )
///: _login( login ), _user_level( level ),
/// _times_logged( 1 ), _guesses( 0 ), _correct_guesses( 0 )
///{}
cout<<anna;
anna.bump_guess_count( 27 );
anna.bump_guess_correct( 25 );
anna.bump_login_count();
cout<<anna;//测试一下这个类对象的数据成员初始化的对不对
cout << "OK: enter user profile data by hand to see if we can read it\n";
//这个不走重载<<运算符函数因为<<右边不是类对象
cin>>anon;//往原来定义的anon类对象输入数据进去,用到>>重载运算符函数。如果等级瞎输入,就默认为beginnner
cout<<anon;//然后输出类对象anon数据成员的数据(包括我们刚输入进去的数据成员的数据。)
return 0;
}
一个非常非常非常非常值得注意的报错问题:报错:undefined reference to `UserProfile::UserProfile()’|!!!!!!!!!!!!!!!!!!!!!!!!!!!!
原因:类构造函数的定义放在定义类主体的头文件里即可!!!!
记住他!!!!
报错问题4.4
其他报错问题:
①error: expected identifier before ‘return’| 报错原因:const后{写成了(导致大括号不匹配
②error: ‘void UserProfile::bump_guess_count(int)’ cannot be overloaded| 报错原因:抄错了。。。。count应该是correct
③给出警告warnings,应该在这些数据成员(私有成员)在声明时一起初始化它。不过这无大碍
warning: ‘int UserProfile::_times_logged’ [-Wreorder]|
warning: when initialized here [-Wreorder]|
④error: stra_itoay ‘\243’ in program|,错误原因代码里有非英文格式的标点符号
⑤_itoa()函数需要包含#include<cstdlib>
头文件,否则报错没有找到_itoa()这个类型
⑥报错error: no match for ‘operator&&’ (operand types are ‘const string
{aka const std::__cxx11::basic_string<char>
}’
and ‘__gnu_cxx::__enable_if<true, bool>::__type {aka bool}’)|
原因:==写成了=(多出现于重载运算符的问题)
程序执行结果:
4.5
//Matrix.h
using namespace std;
//头文件中必须包含该语句(using namespace std),#include<iostream>可以不包含,无影响。
//否则如果用到istream和ostream类型,编译器会报错
//error: 'ostream' does not name a type|
typedef float elemType;//似乎是为了灵活性,可以让矩阵内的数据是别的类型的比如int、double
//方便我们转为模板形式
class Matrix
{
friend Matrix operator+(const Matrix&,const Matrix&);
friend Matrix operator*(const Matrix&,const Matrix&);
//因为这两个重载运算符函数里需要访问Matrix类的私有成员,所以要把
//这两个重载运算符函数设置成友元函数。这两个重载运算符用于矩阵加法和乘法
//写在这里完全是习惯问题。但带friend的函数声明必须写在类主体内
//以便知道友元函数跟哪个类是friend关系
public:
Matrix(elemType=0.,elemType=0.,elemType=0.,elemType=0.,
elemType=0.,elemType=0.,elemType=0.,elemType=0.,
elemType=0.,elemType=0.,elemType=0.,elemType=0.,
elemType=0.,elemType=0.,elemType=0.,elemType=0.);
//类构造函数的重载形式之一的声明(16个参数(默认参数值是0.0),组成4x4矩阵
//注意这个写法0. 代表0.0,记住!
//不需要给Matrix类提供拷贝构造函数,析构函数,拷贝赋值运算符
//因为我们没从堆内存里开辟变量(比如类数据成员)的空间
//没new 什么变量(比如类数据成员)
Matrix(const elemType*);
//类构造函数的重载形式之二的声明( )
void operator+=(const Matrix&);
//重载+=运算符函数声明,用于矩阵加法的重载+运算符函数里。
inline elemType& operator()(int row,int column)
{
return _matrix[row][column];
}
//重载()运算符函数定义用于返回Matrix类的私有成员(矩阵)
//(用于重载*运算符函数的形参m1和m2矩阵)
//下面到private:的上一句是简化“转换至通用型矩阵”的过程(函数声明和函数定义)
inline int rows()const{return 4;}
inline int cols()const{return 4;}
//Matrix类成员函数
inline elemType operator()(int row,int column)const
{return _matrix[row][column];}
//重载()运算符的重载函数定义,用于返回Matrix类的私有成员(矩阵)
//(用于重载*运算符函数的定义的result矩阵(类对象))
ostream& print(ostream&)const;
//打印矩阵所有元素的函数声明
private:
elemType _matrix[4][4];//类数据成员————矩阵的定义
};
//Matrix.cpp
#include<iostream>//这里不能去掉这个语句,否则报错
//error: 'ostream' does not name a type|
#include"Matrix.h"
//error:multiple definition of `Matrix::Matrix(float, float, float,
//float, float, float, float, float, float, float, float, float, float,
// float, float, float)'|
//报错原因:类构造函数定义放在了.h文件,应该放在.h文件附属的.cpp文件
Matrix::Matrix(elemType a11,elemType a12,elemType a13,elemType a14,
elemType a21,elemType a22,elemType a23,elemType a24,
elemType a31,elemType a32,elemType a33,elemType a34,
elemType a41,elemType a42,elemType a43,elemType a44)
//error: expected identifier before ')' token|
//报错原因:参表最后一个参数后紧接)而不应该有任何其他东西
//在最后一个参数和)之间
{
_matrix[0][0] = a11; _matrix[0][1] = a12;
_matrix[0][2] = a13; _matrix[0][3] = a14;
_matrix[1][0] = a21; _matrix[1][1] = a22;
_matrix[1][2] = a23; _matrix[1][3] = a24;
_matrix[2][0] = a31; _matrix[2][1] = a32;
_matrix[2][2] = a33; _matrix[2][3] = a34;
_matrix[3][0] = a41; _matrix[3][1] = a42;
_matrix[3][2] = a43; _matrix[3][3] = a44;
}
//类构造函数(默认形式,带参表,参数16个),给Matrix类私有成员————矩阵进行
//逐个二维数组元素的一对一初始化
Matrix::Matrix(const elemType *array)
{
int array_index=0;
for(int ix=0;ix<4;++ix)
{
for(int jx=0;jx<4;++jx)
{
_matrix[ix][jx]=array[array_index++];
}
}
}
//类构造函数(默认形式之二,参数1个),给Matrix类私有成员————矩阵进行
//诸葛二维数组元素的一维数组对应初始化(初始化的提供的值按矩阵一行所有元素--
//二行所有元素--三行所有元素--四行所有元素这样的顺序存储在一维数组里)
//重载<<运算符函数的定义,函数体内规定输出形式(再嵌套调用print这个Matrix类成员函数)
ostream& Matrix::print(ostream &os)const
//error: prototype for 'std::ostream& Matrix::print(std::ostream&)' does not match any in class 'Matrix'|
//报错原因:函数声明和定义不符,函数定义原先参表()后未加const
//事实证明using namespace std;没在这个文件里写着不影响print函数的os
{
int cnt=0;
//用来记录打印4x4矩阵(二维数组)的元素个数
//初始化为打印了0个矩阵(二维数组的)元素
for(int ix=0;ix<4;++ix)
//error: expected primary-expression before ';' token|
//error: expected ')' before ';' token|
//error: name lookup of 'ix' changed for ISO 'for' scoping [-fpermissive]|
//error: expected ';' before ')' token|
//报错原因:<4后写了两个;
{
for(int jx=0;jx<4;++jx,++cnt)
{
if(cnt&&!(cnt%8))
{
os<<endl;//如果cnt=0或者cnt为8的倍数,就输出回车符
//也就是把4X4矩阵输出成2x8矩阵(2行8列)的形式(一行输出8个矩阵元素)
//在输出一开始就先输出一个回车符,使输出结果和上面可能有的输出结果岔开来
//使输出结果都看着清晰些
}
os<<_matrix[ix][jx]<<' ';//输出矩阵(二维数组)的每个元素(以空格隔开每个元素)
}
}
os<<endl;
return os;
//warning: no return statement in function returning non-void [-Wreturn-type]|
//警告原因:该函数有返回值,返回类型是ostream&,我没有写return语句所以有警告
}
Matrix operator+(const Matrix &m1,const Matrix &m2)//Matrix类的返回类型,参表两个参数
//类型是const Matrix &类,参数名是m一和m2,m1在重载+的左侧,m2在重载+的右侧
{
Matrix result(m1);
//定义Matrix类对象并采用单参数无默认参数值形式的类构造函数给这个类对象初始化
//两类对象(矩阵)相加(m1+m2)的结果存入(合并到)result类对象(矩阵)里。
//result矩阵的元素的值先被赋值成m1矩阵的元素的值,
result+=m2;//两个矩阵相加(矩阵加法)(用到重载运算符+=函数,下面给出其定义),
//相加结果合并到result矩阵
return result;//将两个矩阵相加后的结果输出出去给带重载运算符+的表达式
}
//下面这个+=重载运算符函数和上面的+重载运算符函数不一样,因为+重载运算符函数
//是Matrix类的友元函数,而+=重载运算符函数是Matrix类的成员函数(public:里的)
void Matrix::operator+=(const Matrix &m)
//error: expected initializer before '+=' token|
//报错原因:operator拼错了
{
for(int ix=0;ix<4;++ix)
{
for(int jx=0;jx<4;jx++)
{
_matrix[ix][jx]+=m._matrix[ix][jx];//这里的+=运算符是普通的运算符
//因为是变量间运算而非类对象间运算
}
}
}
//运用+=重载运算符的语句中,+=右侧的类对象当成实参传给重载+=运算符函数的形参m
//然后+=右侧类对象(矩阵m)的元素对应加到+=左侧的类对象(在重载+运算符函数里是
//result类对象)的元素上。相当于+=右侧的类对象(矩阵m)合并到+=左侧的类对象(result)
Matrix operator*(const Matrix &m1,const Matrix &m2)
{
Matrix result;
//定义Matrix类对象并采用16个参数且默认参数值全为0.0形式的类构造函数给这个类对象初始化
for(int ix=0;ix<m1.rows();++ix)
{
for(int jx=0;jx<m1.cols();++jx)
{
result(ix,jx)=0;//初始化两矩阵相乘后存入的result类对象(矩阵)(挨个result矩阵元素
//初始化成0i,这里用到了重载()运算符函数(返回类型是不带&的那个重载()运算符函数)
//,其函数定义在Matrix.h文件的类主体里
for(int kx=0;kx<m1.cols();kx++)
{
result(ix,jx)+=m1(ix,kx)*m2(kx,jx);
//矩阵乘法的运算规则。两矩阵相乘后的结果矩阵存入result类对象里
//这里用到了重载()运算符函数(返回类型是带&的那个重载()运算符函数,用于m1矩阵和m2矩阵
//返回类型不带&的重载()运算符函数用于result矩阵),注意!
//,其函数定义在Matrix.h文件的类主体里
}
}
}
return result;//把矩阵m1*m2的结果result矩阵(类对象)返回
}
//main.cpp
#include <iostream>
#include"Matrix.h"
using namespace std;
inline ostream& operator<<(ostream& os,const Matrix &m)
{
return m.print(os);
}
//error: no match for 'operator<<' (operand types are 'std::ostream {aka std::basic_ostream<char>}' and 'Matrix')|
//报错原因:重载<<运算符的函数定义没放在main()函数里。
int main()
{
Matrix m;//定义一个类对象(矩阵),编译器自动调用Matrix类构造函数
//(参表参数16个的形式的类构造函数,因为那16个参数提供了默认参数值,用户
//在定义Matrix类对象的时候可以一个参数值都不输入)
cout<<m<<endl;
//通过重载<<运算符函数输出m矩阵的所有元素(按照2x8矩阵形式输出)
elemType ar[16]={1.,0.,0.,0.,0.,1.,0.,0.,
0.,0.,1.,0.,0.,0.,0.,1.};
//给定义类对象并以Matrix类构造函数的单参无默认参数值的形式的类构造函数
//初始化该类对象(矩阵)的私有成员而定义的一维数组(矩阵)ar
Matrix identity(ar);
//定义类对象并以Matrix类构造函数的单参无默认参数值的形式的类构造函数
//初始化该类对象(矩阵)的私有成员
cout<<identity<<endl;//通过重载<<运算符函数输出identity矩阵的所有元素(按照2x8矩阵形式输出)
Matrix m2(identity);//默认数据拷贝的类对象之间的操作。这里用到
//Matrix类构造函数的单参无默认参数值的形式的类构造函数来初始化m2类对象(矩阵)
m=identity;//identity类对象(矩阵)赋值给m类对象(矩阵)
//(直接用普通的赋值运算符,因为没涉及到堆内存里分配空间给变量(比如类数据成员)什么的)
cout<<m2<<endl;
cout<<m<<endl;
//通过重载<<运算符函数输出m2矩阵和m矩阵的所有元素(按照2x8矩阵形式输出)
elemType ar2[16]={1.3f, 0.4f, 2.6f, 8.2f, 6.2f, 1.7f, 1.3f, 8.3f,
4.2f, 7.4f, 2.7f, 1.9f, 6.3f, 8.1f, 5.6f, 6.6f };
//给定义类对象并以Matrix类构造函数的单参无默认参数值的形式的类构造函数
//初始化该类对象(矩阵)的私有成员而定义的一维数组(矩阵)ar2
//注意那个数字后面的f,带f的说明这个数据是float型的,占用4字节保存,
//如果不写,默认double型的占8字节。就是为了跟双精度浮点型做区分,f表示单精度
//不加默认是双精度,注意浮点数带f不是强制类型转换!
Matrix m3(ar2);
//定义类对象并以Matrix类构造函数的单参无默认参数值的形式的类构造函数
//初始化该类对象(矩阵)的私有成员
cout<<m3<<endl;
//通过重载<<运算符函数输出m3矩阵的所有元素(按照2x8矩阵形式输出)
Matrix m4=m3*identity;//矩阵乘法,通过重载*运算符函数计算矩阵m3*矩阵identity
//直接用普通的赋值运算符,因为没涉及到堆内存里分配空间给变量(比如类数据成员)什么的
//得到的矩阵,得到的矩阵(类对象)赋值给m4类对象(矩阵)
cout<<m4<<endl;
//通过重载<<运算符函数输出m4矩阵的所有元素(按照2x8矩阵形式输出)
Matrix m5=m3+m4;//矩阵加法,通过重载+和+=(包含在重载+内)运算符函数计算
//矩阵m3+矩阵m4后得到的矩阵,得到的矩阵(类对象)赋值给m5类对象(矩阵)
//(直接用普通的赋值运算符,因为没涉及到堆内存里分配空间给变量(比如类数据成员)什么的)
//warning: variable 'm5' set but not used [-Wunused-but-set-variable]
//警告原因:类对象m5未使用。缺了漏了类对象或变量的使用(比如输入数据给类对象的数据成员或输出类对象的数据成员的数据什么的)就补上就可以了
cout<<m5<<endl; //通过重载<<运算符函数输出m5矩阵的所有元素(按照2x8矩阵形式输出)
m3+=m4;//运用重载+=运算符函数计算m4矩阵和m3矩阵相加并将计算结果合并到m3矩阵里
cout<<m3<<endl;
//通过重载<<运算符函数输出m3矩阵的所有元素(按照2x8矩阵形式输出)
return 0;
}
4.5报错问题和警告问题
①头文件中必须包含该语句(using namespace std),#include<iostream>
可以不包含,无影响。否则如果用到istream和ostream类型,编译器会报错
error: ‘ostream’ does not name a type|
②error:multiple definition of `Matrix::Matrix(float, float, float,
float, float, float, float, float, float, float, float, float, float,
float, float, float)’|
报错原因:Matrix类构造函数定义放在了.h文件,应该放在.h文件附属的.cpp文件
③error: expected identifier before ‘)’ token|
报错原因:参表最后一个参数后紧接)而不应该有任何其他东西在最后一个参数和)之间
④error: prototype for ‘std::ostream& Matrix::print(std::ostream&)’ does not match any in class ‘Matrix’|
报错原因:(成员)函数声明和定义不符,函数定义原先参表()后未加const
ps:事实证明using namespace std;没在这个文件里写着不影响print函数的os
⑤//Matrix.cpp下面那个
//Matrix.cpp
#include<iostream>
不能去掉这个语句,否则报错
error: ‘ostream’ does not name a type|
⑥error: expected primary-expression before ‘;’ token|
error: expected ‘)’ before ‘;’ token|
error: name lookup of ‘ix’ changed for ISO ‘for’ scoping [-fpermissive]|
error: expected ‘;’ before ‘)’ token|
报错原因:<4后写了两个;
⑦warning: no return statement in function returning non-void [-Wreturn-type]|
警告原因:该函数有返回值,返回类型是ostream&,我没有写return语句所以有警告
⑧error: expected initializer before ‘+=’ token|
报错原因:operator拼错了
⑨error: no match for ‘operator<<’ (operand types are ‘std::ostream {aka std::basic_ostream}’ and ‘Matrix’)|
报错原因:重载<<运算符的函数定义没放在main()函数里。
- 总之这个报错很迷,一会儿让放重载运算符的函数声明到main.cpp文件里,一会儿让放重载运算符的函数定义到main.cpp文件里,那就顺其自然,顺着指出错误的指引决定是把重载运算符的函数声明还是定义放入main.cpp文件
⑩warning: variable ‘m5’ set but not used [-Wunused-but-set-variable]
警告原因:类对象m5未使用。缺了漏了类对象或变量的使用(比如输入数据给类对象的数据成员或输出类对象的数据成员的数据什么的)就补上就可以了
程序输出结果:
分别对应m、identity、m2、m、m3、m4、m5、m3矩阵的所有元素的输出排列
终于完结了!!!第五章,来也!!!!