C++面向对象之封装
文章发布于博客园,转载请注明出处,http://www.cnblogs.com/lifehack/p/4083404.html
在上学的时候学过C++,学的时候不理解,学完也就全忘了,工作中用的是C,跟C++没有半点交集。现在为了某些目的准备学习一下面向对象的思想以及面向对象的特性是如何实现的,面向对象的三个特性是封装继承和多态,不准备关心特定的C++语言才有的知识点.但是某些知识点又绕不开,比如我写运算符重载的时候,不理解什么情况下返回对象,什么情况下返回引用,这点也不全然是C++的语法,当成思想的而不是语言的也解释的通。第一篇笔记是封装,实现一个字符串类,由于刚开始写笔记,写的会比较乱,而且只涉及我所关心的,即使这样也不能保证内容的正确性。内容可能比较随意,想到哪写到哪,请多包涵。
一、引用
int a; int &b = a; 这里b是a的引用,或者叫做a的别名。a和b都代表同一个变量,这时对a和b取地址发现是一样的。在C语言中传递参数有两种方式,一种是直接传递值,如 void swap(int a, int b),在调用函数swap时传入的参数是x,y,此时swap函数中的a和b并不是父函数的x和y,只是值相同,内存中的地址并不相同,a和b相当于x和y的一个拷贝,在swap函数中交换a和b的值并不会影响到父函数的x和y。另一种方式是传递地址,如void swap(int *a, int *b),这时再调用swap时要传递地址swap(&x, &y)。此时a和b也并不是x和y,而是存储的x和y的地址,所以通过*a和*b是能够直接访问x和y的,在此情况下,x和y的值能够被交换。
C++新增了一种,传递引用,由于C++的参数可能是对象,如果按值传递参数,同C一样,也会生成对象的一个拷贝,C语言无法直接传递一个结构体,C++中有拷贝构造函数,所以能传递对象,只是增加了额外的空间和时间。直接传递引用,实际上就是传递实参本身,省去了创建临时对象的开销。指针也能够避免创建临时对象,但指针本身占用存储空间,指针是能够被修改并指向其他内容的(用const修饰指针的话就不能修改指针了),引用则比较省事。
void test(string str) { str.data[0] = 'c'; std::cout<<"in test : " << str << std::endl; } int main() { string a = "123"; test(a); std::cout <<"in main : " << a << std::endl; return 0; }
直接传递对象时,多调用了一次以对象为参数的构造函数,并且test函数内的修改没有影响到main函数的对象,他俩的地址并不一样
constructor with char *, this : 18ff38, str : 471048
constructor with string obj, this : 18fedc, &obj : 18ff38
in test : c23 addr : 0018FEDC
in main : 123 addr : 0018FF38
传递对象的引用时,没有创建临时对象,test函数和main函数的对象是同一个
constructor with char *, this : 18ff38, str : 471048
in test : c23 addr : 0018FF38
in main : c23 addr : 0018FF38
二、const
在string类中,我们有两个地方用到了const,一处是修饰函数的参数,一处是修饰函数。无论const放在什么地方,const的用意就是"不能改变"。
- const int a = 1; a是只读,不能被改变
- int const a = 1; 同上面一样,a是只读,不能被改变
- const int *a = &b; *a的值不能被修改,但是指针a是可以被修改的,*a = 4是错误的,编译器报错,const不能作为左值。 但是a = &c,重新给a赋值是可以的
- int * const a = &b; a的值不能被修改,但是*a是可以被修改的, a = &c是错误的,编译器报错,const不能作为左值。但是*a = 1是可以的
- int const * const a = &b; a和*a都不能被修改
2. const修饰函数,const放在函数的最后,编译器会对这个函数进行检查,在这个函数中任何试图改变成员变量和调用非const成员函数的操作都被视为非法。
三、为什么有些运算符重载时要返回引用,什么情况下返回引用
effective c++条款10描述的是为了实现连锁赋值,如a = b = c, 赋值操作必须返回一个reference指向操作符的左侧实参,这是为你的classes实现赋值操作时应该遵守的协议,但这只是个协议,并无强制性,这段话并没有让我深入理解到底为什么。
经实验,连锁赋值时的确返回引用或者返回对象程序都运行正常,区别是如果返回对象会调用拷贝构造函数生成一个临时对象,即使是a = b这种情况,返回的对象并没有人使用,也会生成一个临时对象。如果返回引用则不会生成临时对象,如果对象比较大,节约的时间和空间会比较可观。
但是+运算符重载不能返回引用。例如在执行 c = b + a 时, 以b对象的重载函数为基础,传入的参数为对象a的引用,由于不能改变对象b的值,需要生成一个临时对象,将对象b和对象c的内容填进去,再返回对象并赋值给c。由于是临时对象,所以不能返回引用,临时对象的生命周期在运算符重载函数的内部,函数返回后,临时对象的内容可能被修改,所以此时应该让c++再生成一个对象的拷贝再赋值给对象c。
四、友元
在某些情况下,允许某个函数而不是整个程序访问类的私有成员,友元允许一个类授权其他函数访问它的非公有成员,友元声明以friend开头,它只能出现在类的声明中。比如std::ostream & operator << (std::ostream &os, string &str) 允许ostream访问string的私有成员data.
五、为什么 if ( "abc" == str ) 是错误的
前情提示:用C++标准库的string类来做这个测试是没有问题的。
用我自己写的string类测试时,编译器报错。c++ primer里的解释是这样的,只有左操作数是该类类型的对象时,才会考虑使用作为类成员的重载操作符,因为这里的左操作数不是类类型,所以编译器试图找到一个内置操作符,它可以有一个C风格的字符串的左操作数和一个string类型的右操作数,事实上并不存在这样的操作符。为什么编译器不自动将字符串转换成string对象呢。书里写的大体上是效率,可能有多个用字符串表示的类,要扫描所有的类并找到以字符串作为属性的类,再找到合适的==操作符重载,为了效率,编译器并不做这些动作,而是直接返回错误。我不是很明白,为什么不直接将字符串转换成右操作数的类型呢,用不着扫描所有类啊。JAVA默认就把C风格字符串转换成了string对象了吧。能解释清楚的朋友可以给我讲解一下,多谢
这种情况下我们写全局的操作符重载
- bool operator == ( const string &, const string & );
- bool operator == ( const string &, const char *);
- bool operator == ( const char *, const string &);
在全局情况下如果只提供第一个重载函数,编译器将会将char *转换成string对象,如果全部提供,则调用相应的重载函数。
而bool operator == ( const char *, const char *) 就不能编译通过了,编译器说最少得有一个参数是类类型
六、面向对象的第一个特性-封装
封装-将数据与操作数据的函数作为一个整体,private属性表示对外不可访问,一般用来标识数据及不想让外部访问的函数。public属性是提供给外部的接口。类是一个完整的个体,隐藏了内部实现,使得代码更安全、更易于维护、简洁、干净。比较一下C语言链表的应用和C++链表的应用,C语言需要手动初始化链表,调用函数时需要传入链表,不如C++来的优雅。
七、普通类内存布局
class MyClass
{
public:
int normalFuncA();
static int staticFuncB();
int normalValA;
static int staticValB;
};
int MyClass::staticValB = 0;
int MyClass::normalFuncA()
{
return 0;
}
int MyClass::staticFuncB()
{
return 0;
}
int main()
{
MyClass a;
MyClass b;
//可以看到对象的大小是4,说明此对象只有一个成员变量normalValA
//地址和成员变量的地址相同, 说明对象里只包含非静态成员变量
printf("&a : %p\r\n", &a); //0018FF44
printf("&a.normalValA : %p\r\n", &a.normalValA); //0018FF44
//对象a和b的normalValA地址不同,说明每个对象都包含独立的成员变量
printf("&b : %p\r\n", &b); //0018FF40
printf("&b.normalValA : %p\r\n", &b.normalValA); //0018FF40
printf("sizeof(a) : %d\r\n", sizeof(a)); //4
a.normalValA = 111;
b.normalValA = 222;
//好吧,其实这个打印没有必要,因为从上面看到两个对象的变量的地址就不一样
//那表示的肯定是不同的值,222不会覆盖掉111
printf("a.normalValA : %d\r\n", a.normalValA); //111
printf("b.normalValA : %d\r\n", b.normalValA); //222
//两个对象的staticValB地址相同,说明是同一个,由所有类对象共享
printf("&a.staticValB : %p\r\n", &a.staticValB); //004384AC
printf("&b.staticValB : %p\r\n", &b.staticValB); //004384AC
a.staticValB++;
b.staticValB++;
//分别对a和b的staticValB加加操作,再打印a.staticValB,结果是2,说明无论是
//a.staticValB或者b.staticValB访问的是同一个变量,又废话了,地址一样啊
printf("a.staticValB : %d\r\n", a.staticValB); //2
printf("a.normalFuncA : %p\r\n", a.normalFuncA); //0040100F
printf("b.normalFuncA : %p\r\n", b.normalFuncA); //0040100F
printf("a.staticFuncB : %p\r\n", a.staticFuncB); //00401019
printf("b.staticFuncB : %p\r\n", b.staticFuncB); //00401019
//每个对象都包含独立的非静态成员变量, 因为变量用来存储数据, 但是成员函数只是
//用来处理数据,没有必要么个对象都有一套函数的副本,浪费空间,从上面打印信息中也
//看到了,无论是普通成员函数还是静态成员函数,都只有一个副本,无论从哪个对象调用
//函数地址都是一样的,但是普通成员函数和静态成员函数相比还是有区别的,普通成员函数
//有this指针,用来表示当前对象,静态成员函数不区分对象,没有this指针,
//普通成员函数在调用时多了一句lea ecx,[ebp-4],把当前对象的地址放在寄存器ecx中,
//也就是ecx里存放的就是this指针
a.normalFuncA(); //lea ecx,[ebp-4]
//call @ILT+10(MyClass::normalFuncA) (0040100f)
b.normalFuncA(); //lea ecx,[ebp-8]
//call @ILT+10(MyClass::normalFuncA) (0040100f)
a.staticFuncB(); //call @ILT+20(MyClass::staticFuncB) (00401019)
b.staticFuncB(); //call @ILT+20(MyClass::staticFuncB) (00401019)
return 0;
}
八、string类实现
请原谅我在C++中的代码加printf
#include <iostream> #include <cassert> #include <vector> class string { friend std::ostream & operator << (std::ostream &, string &); public: string(const char *str = NULL); string(const string &other); string & operator = (const string &other); string & operator = (const char *str); string operator + (const string &other); string operator + (const char *str); string & operator += (const string &other); string & operator += (const char *str); bool operator == (const string &other) const; bool operator == (const char *str) const; char & operator [] (int i) const; void chr(int pos, int c); ~string(); public: char *data; }; string::string(const char *str) { if ( NULL != str ) { data = new char[strlen(str)+1]; strcpy(data, str); } else { data = new char[1]; *data = '\0'; } printf("constructor with char *, this : %x, str : %x\n", this, str); } string::string(const string &other) { data = new char[strlen(other.data)+1]; strcpy(data, other.data); printf("constructor with string obj, this : %x, &obj : %x\n", this, &other); } string & string::operator = (const string &other) { if ( this != &other ) { delete [] data; data = new char[strlen(other.data)+1]; strcpy(data, other.data); } printf("operator = overload with string obj, this : %x, &obj : %x\n", this, &other); return *this; } string & string::operator = (const char *str) { delete [] data; if ( NULL != str ) { data = new char[strlen(str)+1]; strcpy(data, str); } else { data = new char[1]; *data = '\0'; } printf("operator = overload with char *, this : %x, str : %x\n", this, str); return *this; } string string::operator + (const string &other) { string temp; temp.data = new char[strlen(data)+strlen(other.data)+1]; strcpy(temp.data, data); strcat(temp.data, other.data); printf("operator + overload with string obj, this : %x, &obj : %x\n", this, &other); return temp; } string string::operator + (const char *str) { string temp; temp.data = new char[strlen(data)+strlen(str)+1]; strcpy(temp.data, data); strcat(temp.data, str); printf("operator + overload with char *, this : %x, str : %x\n", this, str); return temp; } string & string::operator += (const string &other) { string temp(*this); delete [] data; data = new char[strlen(temp.data)+strlen(other.data)+1]; strcpy(data, temp.data); strcat(data, other.data); printf("operator += overload with string obj, this : %x, &obj : %x\n", this, &other); return *this; } string & string::operator += (const char *str) { string temp(*this); delete [] data; data = new char[strlen(temp.data)+strlen(str)+1]; strcpy(data, temp.data); strcat(data, str); printf("operator += overload with char *, this : %x, str : %x\n", this, str); return *this; } bool string::operator == (const string &other) const { printf("operator == overload with string obj, this : %x\n", this); return ( 0 == strcmp(data, other.data) ) ? true : false; } bool string::operator == (const char *str) const { printf("operator == overload with string obj, this : %x\n", this); return ( 0 == strcmp(data, str) ) ? true : false; } char & string::operator [] (int i) const { printf("operator []] overload, this : %x\n", this); assert(i >= 0 && i < strlen(data)); return data[i]; } std::ostream & operator << (std::ostream &os, string &str) { os << str.data; return os; } string::~string() { delete [] data; } void string::chr(int pos, int c) { data[pos] = c; } /* 下面是从酷壳拿的测试代码 */ void foo(string x) { } void bar(const string& x) { } string baz() { string ret("world"); return ret; } int main() { string s0; string s1("hello"); string s2(s0); string s3 = s1; s2 = s1; foo(s1); bar(s1); foo("temporary"); bar("temporary"); string s4 = baz(); std::vector<string> svec; svec.push_back(s0); svec.push_back(s1); svec.push_back(baz()); svec.push_back("good job"); return 0; }