C++学习笔记 1
C++学习笔记
面向对象
输入与输出
#include <iostream>
using namespace std;
int main(){
char s[30];
cout << "say someting to echo:" << endl;
cin >> s;
cout << s << endl;
return 0;
}
头文件与类声明
防卫式声明
#ifndef __COMPLEX__
#define __COMPLEX__
/*something*/
#endif
前置声明
class和函数的声明,但不实现
类声明(class body)
具体描述类的内容,还包括成员函数的声明
成员函数定义
实现成员函数的内容, ::
主要出现的地方,示例如下:
#ifndef __BOX__
#define __BOX__
/*仅用于展示成员函数定义,并非最规范的写法*/
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
#endif
class模板(template)
类中的一些成员变量的类型,可以在声明时先写一个标签,在具体实例化时再决定。
template<typename T>
class Box
{
public:
T length; // 长度
T breadth; // 宽度
T height; // 高度
// 成员函数声明
T get(void);
void set( T len, T bre, T hei );
};
内联函数(inline)
成员函数定义当然也可以放在类声明中完成,(我在java里经常这么干)这样的函数会成为内联候选人。这样的写法不好,原因稍后解释。
在类声明外的成员函数定义可以在函数名前加 inline
关键字(注意要在函数定义前,而不是函数声明前,也称为用于实现的关键字),该函数会成为内联候选人。
想要真正成为内联函数,有如下限制:
- 不能超过一行,如果超过,编译器会忽视
inline
(存疑) - 不能有循环,switch
- 不能是递归函数
其本质并非是定义位置的不同。内联函数与效率有关。调用一个小函数可能会引发一些不必要的栈空间消耗,为了提升效率,这样的小函数会在编译时候被编译器替换为合适的一句短代码,减少损耗。
inline
只是对编译器的一个建议,编译器未必会采纳。如果太复杂,还是会处理为一个真正的函数的。
所以,写在类定义里面的函数如果足够短小,那么可能内联;如果写了一个复杂的函数,还是应该放到类定义外部。
慎用内联函数,相信编译器的选择。
访问修饰符
访问修饰符加到类定义之中。
public
指示该部分的成员是公开可用的,其余函数和语句可以随意使用。即函数和变量,可以直接.
private
指示该部分的成员是私有私用的,只能被该实例自己或者友元函数调用,其余函数和语句不能直接访问和修改。想要访问,需要使用提供的public函数。
protected
和private类似,这种受保护的成员的不同在于,它在子类中是可以访问的。(后续到继承和派生再补充)
通常情况下,public用于声明一些供外部使用的函数。private用于声明成员变量以及不能由他人调用的函数。这样区分的原因是为了提高封装程度。示例如下:
class complex
{
public:
complex (double r = 0, double i = 0): re (r), im (i) { }
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex *, const complex&);
};
构造函数
构造函数是在类声明中,public下的一个函数。
比较好的写法,是使用初始列的写法,如下:
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
//略
};
如果构造时没有赋值,会使用默认实参。
这样的写法比使用赋值的函数形式会更快。因为在实例化一个对象前有一个初始化的过程,在这个过程中赋值比调用构造函数赋值要快一些。当然构造函数的写法也对,如下:
class complex
{
public:
complex (double r = 0, double i = 0){
re=r;
im=i;
}
//略
};
构造函数可以有很多个,即重载(overload)。
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex()
: re(0),im(0)
{ }
//略
};
这里的写法,是存在一定矛盾的。第一个构造函数已经说明了如果没提供参数,使用默认实参0;第二个又重写了一遍这句话,没必要且会编译不通过。
可以这样写:
class complex
{
public:
complex (double r = 0, double i = 0): re (r), im (i) { }
complex(double a)
: re(a),im(a)
{ }
};
第二个构造函数的意思是,如果只给了一个参数,那么实部虚部都设置为该参数。(实际没什么用)
构造函数可以放在private区里吗?听上去有些无厘头,这样不就无法实例化对象了吗?写一个不需要实例化的类?事实上,这么做也是可以的。
在一种名为单例模式(singleton)的设计模式中,设计一个类,这个类自己负责创建一个对象且只有一个,并且提供一些访问这个对象的函数。对于外部来说,直接使用就可以,不需实例化,全局共享一个实例。这种设计模式的关键就是构造函数是私有函数。普遍用于一些限制实例数目,避免全局使用的实例被频繁创建和回收的场景。代码截图如下:
常量成员函数与const关键字的用法
提供成员值的成员函数(有些绕),这样的函数应该被标记为 const
。调用函数不会也不能改变对象的内容。示例如下:
double real() const {return re;}
double imag() const {return im;}
const int a = 7;
函数的const标记与变量不同,函数的是放到函数名之后花括号之前,变量的是放到类型前。
这样的操作一方面为了保护成员变量,另一方面也避免一个const的对象在获取成员变量时可能出现的问题。
进一步的,如果某个成员函数标记为const,但是定义中想要更改成员变量怎么办?可以在声明成员变量时,在前方加上 mutable
关键字,这个成员可以处于不断地变化中,不会被const成员函数限制。
左定值是指变量值不能变。右定向,是指内存地址不能改,但是内容可以改。当然可以两个都写上,保证内容和内存地址都不改变。左右是指 const
在 *
的左右
const int* p = 8;
int a = 8;
int* const p = &a;
int a = 8;
const int* const p = &a;
参数传递,传值还是传引用?
传引用不错,毕竟指针的大小固定,对象的大小可能很大,引起开销。通常也建议写成传引用。
有的时候也没必要非传引用,尤其是一些简单的变量和数据类型。
如果希望传入的参数不被改变,要使用const来修饰。有下列几种情况:
void function(const int Var); //传递过来的参数在函数内不可以改变(无意义,因为Var本身就是形参)
void function(const char* Var); //参数指针所指内容为常量不可变
void function(char* const Var); //参数指针本身为常量不可变(也无意义, 因为char* Var也是形参)
void function(const TYPE& Var); //引用参数在函数内为常量不可变
在这里第一次见到了 类型名&
这种写法。这表示该类型的引用,与传指针有一定的区别,但可以理解为就是指针。在子函数里,形参和变量名一样使用,改变同时也会影响实参。
C++中,参数到底是指针还是引用是有区别的,以*和&区分,这里先不深究。
返回值,传值还是传引用?
同样需要认真考虑。如果仅需要一个计算结果,那么应该传值。就算结果是一个对象,也应该传值,如果传引用是无效的,因为函数结束后那个引用已经死亡。
但有的时候也要传引用,例如这个引用在参数中传入,处理后又返回,这样是可以的。
友元函数friend
private和protected的成员变量不能被外部调用,只能被自己调用或友元函数调用。友
元函数定义在类外部,有权访问如上的成员变量。友元函数的原型在类定义中出现过,但并不是成员函数。
友元类是指整个类和成员都是友元,可以被友元类访问。
示例如下:
class Box
{
friend class Box2; //Box2是Box的友元类,Box可以直接访问Box的任何成员。
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
// 请注意:printWidth() 不是任何类的成员函数,没有写成Box::printWidth,调用时候也不需要用一个对象.
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
友元函数没有this指针,需要把使用的对象做参数传入。如果是static成员、全局变量、全局对象,可以不需要参数。
此外,相同类的对象之间互为友元,可以调用对方的private变量。代码截图如下:
操作符重载
成员函数法
成员函数有一个隐藏的参数,this
,指的是调用者。this是一个指针。代码示例如下:
//do assignment plus
inline complex& __doapl(complex* ths, const complex& r){
ths->re+=r.re;
ths->im+=r.im;
return *ths;
}
inline complex& complex::operator += (const complex& r){
return __doapl(this ,r )
}
c2+=c1;
注意到,操作符函数的返回值也是引用,如果不关心返回值,设为void也无妨。但是对于如下这种连用操作符:
c3+=c2+=c1;
需要返回值,c2+=c1的返回值也要有。
非成员函数法
对于一个类的一个操作符,只能使用成员或非成员一种写法,不能写两种。编译器会自动匹配。
非成员函数没有this指针,这实际上相当于在写全局函数。
inline complex operator + (const complex& x, const complex& y)
{
return complex (real (x) + real (y), imag (x) + imag (y));
}
inline complex operator + (const complex& x, double y)
{
return complex (real (x) + y, imag (x));
}
inline complex operator + (double x, const complex& y)
{
return complex (x + real (y), imag (y));
}
//对应如下三种用法
c2=c1+c2;
c2=c1+5;
c2=7+c1;
返回值为值value,不能是引用reference。这与之前提到的返回值选哪个道理一样。因为做完加法之后,要把值返回,而不是返回加法函数的局部引用变量,那个引用会销毁的。同时也要一个地方来保存这个对象,使用临时对象。而之前+=返回值为引用,是因为返回值已经放在传入的一个引用里了,返回引用当然也没问题。
同一个操作符,依据参数个数不同也可以有不同的效果,如下:
inline complex operator + (const complex& x)
{
return x;
}
inline complex operator - (const complex& x)
{
return complex (-real (x), -imag (x));
}//negate
对于<<
,只能写非成员函数。代码如下:
#include <iostream.h>
ostream& operator << (ostream& os, const complex& x){
return os << '(' << real(x) << ','
<<image(x)<<')';
}
os不能写成 const
,因为输出就是在改变os。
为了可以连串输出,返回值应该为 osteam&
,这个可以继续接受<<,所以应该和cout是一个类型。
临时对象
类名称typename(),相当于用构建函数创建个临时对象,生命周期很短,只是为了传递值,没有名字也无所谓。
引用很好
传送者无需知道接收者是以reference引用形式接收。
比如doapl函数,return的是 *ths
,是一个对象,但是声明的返回值也就是接收者用的是引用形式。这样写没有问题,具体内容交给编译器来处理。
再比如使用重载的操作符时候,c1也是对象形式传入,但参数写的是引用,也没问题。
全域(全局)函数
不带类名称就可以。但是使用的时候要把对象作为参数传入,不像成员函数可以直接点。
一些要注意的写法
- 构造函数要写初始化
- 成员函数要不要加const
- 参数传递尽量用引用,要不要const
- 返回值是值还是引用
- 数据要放private,函数要放public