1、类和结构
类描述看上去很像是包含成员函数以及public和private可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有与类相同的特性。他们之间唯一的区别是,结构的默认访问类型是public,而类为private。C++程序员通常使用类来实现类描述,而把结构限制为只表示纯粹的数据对象或没有私有部分的类。
class World
{
float mass; //private by default
char name[20]; //private by default
public:
void tellall(void);
...
};
类的命名规则中,一种常见但不通用的约定——将类名首字母大写。
一般来说,访问私有类成员的唯一方法是使用类方法。C++使用友元函数来避开这种限制。要让函数成为友元,需要在类声明中声明该函数,并在声明前加上关键字friend。
类的成员函数的两个特征:
- 定义成员函数时,使用作用域解析符(::)来标识函数所属的类。
- 类方法可以访问类的private组件。即,类的成员函数可以访问私有数据成员。
#include <iostream>
#include<cstring>
using namespace std;
class Stock
{
private:
char company[30];
int shares;
double share_val;
double total_val;
void set_tot(){total_val=shares*share_val;}
public:
void acquire(const char * co,int n,double pr);
void buy(int num,double price);
void sell(int num,double price);
void update(double price);
void show();
};
void Stock::acquire(const char *co, int n, double pr)
{
std::strncpy(company,co,29);
company[29]='\0';
if(n<0)
{
std::cerr<<"Number of shares can't be negative. "
<<company<<" shares set to 0.\n";
shares=0;
}
else
shares=n;
share_val=pr;
set_tot();
}
void Stock::buy(int num, double price)
{
if(num<0)
{
std::cerr<<"Number of shares purchased can't be negative. "
<<"Transaction is avorted.\n";
}
else
{
shares+=num;
share_val=price;
set_tot();
}
}
void Stock::sell(int num, double price)
{
using std::cerr;
if(num<0)
{
cerr<<"Number of shares sold can't be negative. "
<<"Transaction is aborted.\n";
}
else if(num>shares)
{
cerr<<"You can't sell more than you have! "
<<"Transaction is aborted.\n";
}
else
{
shares-=num;
share_val=price;
set_tot();
}
}
void Stock::update(double price)
{
share_val=price;
set_tot();
}
void Stock::show()
{
using std::cout;
using std::endl;
cout<<"Company: "<<company
<<" Shares: "<<shares<<endl
<<" Share price: $"<<share_val
<<" Total Worth: $"<<total_val<<endl;
}
int main(int argc, char *argv[])
{
using std::cout;
using std::ios_base;
Stock stock1;
stock1.acquire("NanoSmart",20,12.50);
cout.setf(ios_base::fixed);
cout.precision(2);
cout.setf(ios_base::showpoint);
stock1.show();
stock1.buy(15,18.25);
stock1.show();
stock1.sell(400,20.00);
stock1.show();
return 0;
}
格式化命令:
cout.setf(ios_base::fixed);//use fixed decimal point format
cout.precision(2);//two places to right of decimal
cout.setf(ios_base::showpoint);//show trailing zeros
2、名称空间
名称空间允许定义一个可在其中声明标识符的命名区域。这样做的目的是减少名称冲突,尤其当程序非常大,并使用多个厂商的代码时。可以通过使用作用域解析操作符、using声明或using编译指令,来使名称空间中的标识符可用。
//file1.cpp
#include<iostream>
using namespace std;
void other();
void another();
int x=10;
int y;
int main()
{
cout<<x<<endl;
{
int x=4;
cout<<x<<endl;
cout<<y<<endl;
}
other();
another();
return 0;
}
void other()
{
int y=1;
cout<<"Other: "<<x<<", "<<y<<endl;
}
//file2.cpp
#include<iostream>
using namespace std;
extern int x;
namespace
{
int y=-4;
}
void another()
{
cout<<"another(): "<<x<<", "<<y<<endl;
}
3、复制构造函数 P386
复制构造函数用于将一个对象复制到新创建的对象中,用于初始化过程中。类的复制构造函数原型通常如下:
Class_nae(const Class_name &); //接收一个指向类对象的常量引用作为参数。
(1)何时调用复制构造函数?
a、新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
例如,假设motto时一个Stringbad对象,则下面4种声明都将调用复制构造函数:
StringBad ditto(motto); //calls StringBad(const StringBad &)
StringBad metoo=moto; //calls StringBad(const StringBad &)
StringBad also=String(motto); //calls StringBad(const StringBad &)
StringBad * pStringBad=new StringBad(motto); //calls StringBad(const StringBad &)
其中,中间的2种声明会使用复制构造函数直接创建metoo和also,也可能使用复制构造函数生成一个临时对象,然后将临时对象的内容赋给metoo和also。
b、当函数按值传递对象或者函数返回对象时,都将调用复制构造函数。默认的复制构造函数将会复制原始变量的地址,它将指向原始变量的地址。
由于按值传递对象将调用复制构造函数,因此应该按引用传递对象。这样可以节省调用构造函数的时间以及存储新对象的空间。按引用传递对象时,将会进行深度复制,此时复制构造函数将复制字符串并将副本的地址赋给str成员,而不仅仅是复制字符串地址。
(2)复制构造函数的功能
默认的复制构造函数将逐个复制非静态成员,复制的是成员的值。静态函数不受影响,因为静态函数属于整个类,而不是各个对象。
StringBad sailor=sports;
等同于:
StringBad sailor;
sailor.str=sports.str;
sailor.len=sports.len;
这里复制的并不是字符串,而是一个指向字符串的指针。也就是说,将sports初始化为sailor后,得到的两个指向同一个字符串的指针。
//StringBad.h
#include<iostream>
class StringBad
{
private:
char * str; //pointer to string
int len; //length of string
static int num_strings; //number of objects
//enum{num_strings2=0};
//static const int num_strings3=0;
public:
StringBad(const char * s);
StringBad();
~StringBad();
friend std::ostream & operator<<(std::ostream & os,
const StringBad & st);
};
//StringBad.cpp
#include<cstring>
#include"strngbad.h"
using std::cout;
int StringBad::num_strings=0;
StringBad::StringBad(const char *s)
{
len=std::strlen(s);
str=new char[len+1];
std::strcpy(str,s);
num_strings++;
cout<<num_strings<<": \""<<str
<<"\" object created\n";
//str=s;
}
StringBad::StringBad()
{
len=4;
str=new char[4];
std::strcpy(str,"C++");
num_strings++;
cout<<num_strings<<": \""<<str
<<"\"default object created\n";
}
StringBad::~StringBad()
{
cout<<"\""<<str<<"\" objet deleted, ";
--num_strings;
cout<<num_strings<<" left\n";
delete [] str;
}
std::ostream & operator<<(std::ostream & os,const StringBad & st)
{
os<<st.str;
return os;
}
//useStringBad.cpp
#include<iostream>
#include "strngbad.h"
using std::cout;
void callme1(StringBad &);
void callme2(StringBad &);
int main()
{
using std::endl;
StringBad headline1("Celery Stalks at Midnight");
StringBad headline2("Lettuce Prey");
StringBad sports("Spinach Leaves Bowl for Dollars");
cout<<"headline1: "<<headline1<<endl;
cout<<"headline2: "<<headline2<<endl;
cout<<"sports: "<<sports<<endl;
callme1(headline1);
cout<<"headline1: "<<headline1<<endl;
callme2(headline2);
cout<<"headline2: "<<headline2<<endl;
cout<<"Initialize one object to another: \n";
StringBad sailor=sports;
cout<<"sallor: "<<sailor<<endl;
cout<<"Assign one object to another: \n";
StringBad knot;
knot=headline1;
cout<<"knot: "<<knot<<endl;
cout<<"End of main()\n";
return 0;
}
void callme1(StringBad & rsb)
{
cout<<"String passed by reference: \n";
cout<<" \""<<rsb<<"\"\n";
}
void callme2(StringBad & sb)
{
cout<<"String passed by value: \n";
cout<<" \""<<sb<<"\"\n";
}
不允许再次释放已经释放过的内存,当再次尝试释放已经释放的内存时,程序会根据编译器的差异报出不同的不可预料的错误。
其中,默认的复制构造函数不说明其行为,因此它不创建过程,也不增加计数器num_strings的的值。但析构函数更新了计数,并且在任何对象过期时都将被调用,而不管对象时如何被创建的。
该程序应提供一个队计数进行更新的显示复制构造函数:
String::String(const String & s)
{
num_string++;
...//import stuff to go here
}
如果类中包含这样的静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显示复制构造函数来处理计数问题。
如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这杯称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅地复制指针信息,而不会复制指针引用的结构。
String::String(const String & st)
{
num_strings++;
len=st.len;
str=new char [len+1];
std::strcpy(str,st.str);
}
//检查自我赋值的情况,释放成员指针以前指向的内存,复制数据而不仅仅是数据的地址,并返回一个指向调用对象的引用。
String & String::operator=(const String & st)
{
if(this==&st)
return *this;
delete [] str;
len=st.len;
str=new char[len+1];
std::strcpy(str,st.str);
return *this;
}
4、作用域和链接性
C++存储方案决定了变量保留在内存中的时间(存储持续性)以及程序的哪一部分可以访问它(作用域和链接性)。自动变量实在代码块(如函数体或函数体中的代码块)中定义的变量,仅当程序执行到包含定义的代码块时,它们才存在,并且可见。自动变量可以通过使用存储类型说明符auto或register或者根本不适用说明符(与使用auto相同)来声明。register说明符提示编译器,该变量的使用频率很高。
静态变量在整个程序执行期间都存在。对于在函数外面定义的变量,其所属文件中位于该变量的定义后面的所有函数都可以使用它(文件作用域),并可在程序的其他文件中使用(外部链接性)。另一个文件要使用这种变量,必须使用extern关键字来声明它。对于文件间共享的变量,应在一个文件中包含其定义声明(不使用extern),并在其他文件中包含引用声明(使用extern)。在函数的外面使用关键字static定义变量的作用域为整个文件,但是不能用于其他文件(内部链接性)。在代码中使用关键字static定义变量被限制在该代码块内(局部作用域、无链接性),但在整个程序执行期间,它都一只存在并且保持原值。
在默认情况下,C++函数的链接性为外部,因此可在文件间共享;但使用关键字static限定的函数的链接性为内部的,被限制在定义它的文件中。
5、自动变量和堆栈 P269
正确理解自动变量在函数中占用内存和释放内存。
函数在被调用时,将函数中的自动变量自动添加到堆栈;然后在函数开始执行后,将函数中的形参和刚添加到堆栈上的值关联起来;当函数结束后,栈顶指针被重置为函数被调用前的值,从而释放自动变量使用的内存。
进入堆栈中的局部变量遵循先进后出原则。