C Plus学习笔记
# 阅读笔记 - 才发现CC为标准UNIX C++编译器,而cc为标准UNIX C编译器。g++为GNU C++编译器,而GNU C++编译器的MS-DOS版本名为gpp。comeau编译器最为严格、标准。都是先生成.o文件,再生成.out文件。 - 在C中,main函数括号为空表示对是否接受参数保持沉默,而在C++中为空与跟void一样。C++中main函数默认返回0。 - cout是可扩展的,允许自行开发新的数据类型。 - short至少16位;int至少和short一样长;long至少32位,且至少与int一样长。 - sizeof对类型名使用时,必须加上括号。 - C++使用前一(两)位来标识数字常量的基数。如果第一位是1~9,则基数为10;如果第一位为0,第二位为1~7,则基数为8;如果前两位为0x或0X,则基数为16。 - 数据后缀标识存储的类型,如2344L,默认情况下为int型,除非太大,int装不下。 - char在默认情况下既不是有符号,也不是没符号。在符号很重要的情况下,一定要特别声明。 - E表示法适合于非常大和非常小的浮点数。如:+3.45E+6指的是3.45与1000000相乘的结果,E后为负数代表除以10的乘方。 - cout.setf(ios_base::fixed, ios_base::floatfield);,这样可以显示浮点数多余的零。 - 赋值计算在类型转换时,较大的数转换为较小的数时,原来的值可能超过目标类型的取值范围,这种情况结果将是不可确定的。将浮点型转换为整形时,C++采取截取(丢弃小数部分),而不是四舍五入。 - true被转换为1,false被转换为0,这些转换称为整形提升(int类型计算速度更快)。 - 传统C语言总是将float提升为double,即使两个操作数都是float。 - 强制类型转换的格式为:(typeName)value/typeName(value),第一种来自C语言,第二种来自C++语言。 - 用引号括起的字符串隐式的包括了结尾的空字符。如“S”表示两个字符,而‘S’单表示一个字符,更糟糕的是,“S”表示的是一个内存的地址。 - strlen函数返回的是存储在数组中字符串的长度,而不是数组本身的长度。另外strlen不会计算空字符。 - 为了能够输入多个单词,可以使用cin.getline()函数和cin.get()函数,getline丢弃了换行符,存储时用空字符替换,而get保留在了输入序列中,所以在使用这种方法时,需要不带任何参数的cin.get()调用读取下一个字符,也可以连续调用,如cin.get(name, ArSize).get()。 - string在C++中是作为类出现。string类具有自动调整大小的功能。输入字符串的方法为getline(cin, str)。
1 | for (int i = 0; i < SIZE; i++) |


1
2
3
4
5
ofstream outFile;
outFile.open("fish.txt");
double wt = 125.9;
outFile << wt;
cin >> total >> choices,如果输入4 2,那么total为4,choice为2。
在C++中,当且仅当用于函数头或函数原型中,int *arr 和int arr[]的含义才相同。
二维数组函数定义:
1
2
3
4
5
6
7
8int sum(int ar2[][4], int size)
{
int total = 0;
for (int r = 0; r < size; r++)
for(int c = 0; c < 4; c++)
total += ar2[r][c];
return total;
}当结构较小时,按值传递最合理。较大时使用按址传递。
递归案例:ruler.cpp
函数指针基础知识
函数指针声明。
<img src="https://nephen-blog.oss-cn-beijing.aliyuncs.com/post/20250607221614.png"> 例如 void estimate(int lines, double (*pf) (int));
使用指针来调用函数,见例子。
1
2
3
4
5
6double pam(int);
double (*pf) (int);
pf = pam;
double x = pam(4);
double y = (*pf)(4);
double z = pf(4);内联函数不能递归。与C语言的宏类似。只需在定义和声明前加上inline即可。
必须在声明引用时将其初始化。
注意
:在C++中,使用const,当形参的类型不对或不是左值时,将会自动创建临时变量,这样将不能修改作为参数传递的变量。结构引用:const sysop & use(sysop & sysopref);,请见例程/类对象,应避免返回函数终止时不再存在的内存单元引用。通过传递引用而不是整个数据对象,可以提高程序的运行速度。
注意在返回值加上const,意味着不能使用返回的引用直接修改它指向的引用。参数类型为ostream &的函数可以接受ostream对象或声明的ofstream对象作为参数,见文件类。设置类如下
指导原则:
显示具体化的原型和定义应以template<>打头,并通过名称来指出类型,这样可以只交换结构体中的一部分成员。不要使用Swap()模板来生成函数定义,而应使用独立的、专门的函数显式的为job类型生成函数定义,如
template <> void Swap<job>(job&, job&);
,其中Swap<job>
中的<job>
是可选的。<例子>编译器使用模板为特地类型生成函数定义时,得到的是模板实例,有隐式实例化和显式实例化。显式实例化如template void Swap
(int, int);,它与显式具体化不同,template后面没有<>。而隐式实例化指的是一般的模板调用。 警告
:试图在同一编程单元中使用同一类型的显式实例和显式具体化将出错。隐式实例化、显式实例化和显式具体化统称为具体化,它们都使用具体类型的函数调用,而不是通用描述。
在重载解析中,哪一种执行的转换最少,将被优先得到执行。如果出现多个匹配或没有,都将出现错误。<例子>
变量的一些特性:存储持续性、作用域和链接性。寄存器变量也是auto变量,只是它是用寄存器而不是堆栈来处理变量。链接分为:外链接、内链接和无链接。包含字符串输入的static例子
mutable可以指出,即使结构或类为const,其某个成员也可以被修改。
在C++看来,全局const定义就像使用了static说明符一样。如果链接为外部的,extern const int states = 50;
布局new操作符、常规new操作符。布局操作符如:p1 = new (placement) int;,或pd2 = new (buffer + N*sizeof(double)) double[N];。布局操作符不需delete。<例子>
名称空间:namespace,其链接性是外部的,可以将名称空间声明进行嵌套。
可以给名称空间创建别名:namespace mvft = my_very_favorite_things {…};
简化:namespace MEF = myth::elements::fire; using MEF::flame;接收一个参数的构造函数允许使用赋值句法来将对象初始化为一个值,也可以看作为强制类型转换,可以使用explicit 关闭隐式转换,但仍然可以进行显式转换:myCat = Stonewt(19.6);,当然19.6也可以是int型,会自动转换为double型。<例子>
如何将类类型转换为其它内置类型?使用特殊的C++操作符函数——转换函数。如:Stonewt::operator int() const;,其返回类型可以是被声明为目标类型的类成员函数:Star::Star double();。<例子> 注意一下几点:
转换函数必须是类方法。
转换函数不能指定返回类型,但也能返回所需的值。
转换函数不能有参数。
如果不想被隐式的转换,可以换成int Stonewt::Stone_to_Int();,然后作为方法调用即可。
const成员函数:void Stock::show()const,不会修改对象的值,这里的const表示show(const Stock *this),this指向调用的对象。
对象数组初始化像普通的数组一样。要创建对象数组,则这个类必须有默认的构造函数,但只能有一个默认构造函数,用来设定特定的值,因为如下花括号中的构造函数只是创建临时对象。
1
2
3
4
5
6const int STKS = 10;
Stock stocks[STKS] = {
Stock("NanoSmart", 12.5, 20),
Stock(),
Stock("Monolithic Obelisks", 130, 3.25),
}类只是描述了对象的形式,并没有真正创建对象,因此,在被对象创建之前,并没有用于存储值的空间。可以在类声明中声明枚举为整形常量,并且不需要提供枚举名。也可以采用关键字static,如static const int LEN = 30,但这个不能存储double常量,如果不是const,则应在定义方法时进行初始化,并且应该加上类限定符。
操作符重载限制:重载后的操作符必须至少有一个操作数是用户定义的类型。不能违反操作符原来的句法。不能修改操作符优先级。不能定义新的操作符。
友元有三种:友元函数、友元类和友元成员函数。
有一类特殊的非成员函数可以访问类的私有成员,它们被称为友元函数。非成员函数可以解决的问题:A = operator(2.75, B);,因为这个时候的第一个操作数即调用者不为自身对象,只能使用非成员函数。另外还有一种方法是,将2.75强制转换为对象。
注意
:虽然友元函数是在类声明中声明的,但它不是成员函数,因此不能使用成员操作符调用。它不是成员函数,但与成员函数访问权限相同。不要在定义中使用关键字friend,除非定义也是原型。类声明可以控制哪些函数可以访问私有数据,类方法和友元只是表达类接口的两种不同机制。打印出Time类trip,重载操作符<<
1
2
3
4void operator <<(ostream & os, const Time & t)
{
os << t.hours << " hours. " << t.minutes << " minutes";
}即可使用cout << trip
如果要实现拼接,如cout << “Trip Time: “ << trip << “ (Tuesday)\n”;
可返回os的引用,如下1
2
3
4
5ostream & operator <<(ostream & os, const Time & t)
{
os << t.hours << " hours. " << t.minutes << " minutes";
return os;
}由于类继承属性让ostream引用能够指向ostream对象和ofsream对象,所以还可以将其写入文件中。<例子>
加法操作符需要两个操作数。对于成员函数版本来说,一个操作数通过this指针隐式的传递,另一个操作数作为函数参数显式传递;对于友元函数来说,两个操作数都作为参数来传递。
隐式成员函数包括:默认构造函数、复制构造函数、赋值操作符、默认析构函数和地址操作符。
复制构造函数:新建一个对象并将其初始化为同类现有对象时,复制构造函数都将会调用。即每当程序生成对象副本时,编译器都将使用复制构造函数。如:函数按值传递对象时,因此应该按引用传递对象,这样可以节省调用构造函数的时间以及存储新对象的空间。
赋值操作符:将一个已有的对象赋给另一个对象时,将使用重载的赋值操作符,来实现成员的逐个复制。解决的办法也是进行深复制。<例子>需要注意几点:
由于目标对象可能引用了以前分配的对象,所以应使用delete []释放。
应该避免将对象赋给自己。
返回一个指向调用对象的引用。
new对应于delete,delete []与new []初始化的指针和空指针都兼容。如:str = new char[1]比str = new char要好。如果有多个构造函数,则必须以相同的方式使用new,要么都带中括号,要么都不带,因为只有一个析构函数。
返回对象将调用复制构造函数,而返回引用不会。返回对象将调用复制构造函数和析构函数,有时候会浪费内存和时间。如:返回类型必须是ostream &,不能使用ostream,因为ostream没有公用的复制构造函数。如果被返回的对象是被调用函数中的局部变量,则应按对象方式返回,通过调用构造函数生成,这样的例子如重载加减运算符。
将加法重载操作符的返回值设为const,这样force1 + force2 = net;这样的语句将称为非法语句。
使用布局new操作符为对象分配内存时,防止出现内存重叠,可以这样:pc1 = new (buffer) JustTesting; pc3 = new (buffer + sizeof(JustTesting)) JustTesting;,释放内存应该这样:delete [] buffer;,并显式的调用析构函数pc3->
JustTesting();pc1->JustTesting();。<例子>引用和const一样,只能在对象创建时进行初始化。对于简单数据成员,使用成员初始化列表和在函数体中进行赋值并没有什么区别,不过效率更高。成员初始化列表只能用于构造函数。数据成员列表被初始化的顺序与它们在类声明中的顺序相同,与初始化列表的排列顺序无关。这使得初始化内置类型就像初始化类对象一样。
为了防止对象未定义深度复制构造函数而造成程序崩溃,可以在私有部分定义空的复制构造函数和重载空的赋值操作符。
1
2
3
4
5
6
7class Queue
{
private:
Queue(const Queue & q) : qsize(0) {}
Queue & operator =(const Queue & q) { return *this; }
//...
};构造函数必须给新成员(如果有的话)和继承的成员提供数据。由于派生类不能直接访问基类的私有成员,所以,派生类构造函数必须使用基类构造函数。创建派生类对象时,程序首先创建基类对象,使用成员初始化列表。
除非要使用默认构造函数,否则应显式调用正确的基类构造函数。 如果没有使用动态内存分配,在派生类构造函数里使用基类的隐式复制构造函数是可以的。 释放对象的顺序与创建对象的顺序相反,即首先执行派生类的析构函数,然后自动调用基类的析构函数。[<例子>](https://github.com/nephen/cPrimerPlus/tree/master/chapter13/usett0.cpp)