遇如下输入语句,如何结束输入跳出循环呢——输入文件结束符(或输入错误,比如不是int型)——WINDOWS系统中,输入结束符的方法:Ctrl + Z
,然后enter or return
键。 UNIX(包括Mac OS X)系统中,Ctrl + D
.
while (cin >> value) {
sum += value;
}
每个类实际上都定义了一个新的类型,其类型名就是类名。而类型后面的就是一个属于该类型的变量。如定义了一个类Sales_item,则定义一个类类型的变量:
Sales_item item;
item 是一个Sales_item类型的对象。
#include
包含来自标准库的头文件时,用尖括号<>
包围头文件名,否则用双引号""
包围。
文件重定向:这种机制允许我们将标准输入和标准输出与命名文件关联起来
$addItems
假定$是操作系统提示符,加法程序已编译为addItems.exe的可执行文件,则上述命令会从一个名为infile的文件读取销售记录,并将结果写入到一个名为outfile的文件中,两个文件都位于当前目录。
成员函数是定义为类的一部分的函数,有事也被称之为方法。如:
Item.isbn();
// .点运算符只能用于类类型的对象。左边是对象,右边是成员名,效果是调用该类型的函数,()是调用运算符,里面放置实参列表(可为空)。
基本数据类型(内置类型):算数类型、空类型(不对应具体的值)
算数类型:整数型(包括字符、布尔值)、浮点型
内置基本类型:整数型(包括字符、布尔值)、浮点型、void、枚举;
int、short、long、long long
都是带符号的,在前添加unsigned
为无符号类型,unsigned int
可缩写为unsigned
。
字符型分为三种:char、signed char、unsigned char
。char
有无符号由编译器决定。
float
为单精度浮点型,有效数字为6~7位;double
为双精度,有效数字为15~16; 但他们在输出时,小数点后都有6位小数。
float
有效位的解释:从左往右数,前6位一定是有效的(精确),所以精确度是6位。意思是前6位一定精确,6位以后的可能精确,可能不精确。
如何选择类型的几点注意:
unsigned
int
和long
尺寸相同,所以超出int
范围时,用long long
char
由于机器不同所表现的类型不同,容易出问题,所以尽量明确指定是signed char
或者unsigned char
double
,float
经常精度不够并且与double
的计算代价相差无几,但long double
的消耗很大。当给无符号类型一个超出范围的值时,结果是对该值对无符号数值总数取模后的余数。例如
unsigned char c = -1; //c=255;
数学角度:
-1对256求模mod(-1,256)
取模运算时,对于负数,应该加上被除数的整数倍,使结果大于或等于0之后,再进行运算.
也就是: (−1)%256=(−1+256)%256=255%256=255 ( − 1 ) % 256 = ( − 1 + 256 ) % 256 = 255 % 256 = 255
计算机角度:
计算机中负数是以补码形式存储的,负数的补码是对应正数的原码取反加1得到,-1的补码11111111,转换成无符号数即是255的二进制编码。
注释:通常,取模运算也叫取余运算,它们返回结果都是余数.rem(取余)和mod(取模)唯一的区别在于: 当x和y的正负号一样的时候,两个函数结果是等同的;当x和y的符号不同时,rem函数结果的符号和x的一样,而mod和y一样。取余运算在取商的值时,向0 方向舍入(fix()函数);而取模运算在计算c的值时,向负无穷方向舍入(floor()函数)。
当一个算数表达式中既有无符号数又有有符号数(如int)值时,那个int值就会转化为无符号型。例如:
unsigned a=10;
int b=-1;
a+b的值是-1转化为unsigned值后再加上10.
字面值常量
整形字面值:20十进制 020八进制 0x2 十六进制
默认情况下,十进制是带符号数,八进制和十六进制即可能是带符号的,也可能是无符号的。类型short没有对应的字面值。
严格来说,十进制字面值不会是负数。-10的负号并不在字面值之内,是对字面值取负值。
默认的,浮点型字面值是double。
‘a’ //字符字面值 char
“abcdef” //字符串字面值 以空字符‘\0’结尾,实际长度比内容多1.
true,false //布尔字面值
nullptr //指针字面值
变量 提供一个具名的、可供程序操作的存储空间。变量和对象一般可以互换使用。
string
是一种库类型,表示一个可变长的字符序列 string book(“0-201-78-X”);
初始化不是赋值,初始化是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除而以一个新值来替代。初始化方式有以下四种:
int a=0;
int a={0}; //初始化列表,如果初始值存在丢失信息的风险,则编译器会报错,如小数转化为整形。
int a{0};
int a(0);
内置类型的变量未被显示初始化,它的值由定义的位置决定。定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量将不被初始化(值未定义,访问或拷贝将出错)。
未初始化的变量含有一个不确定的值。
绝大多数类都支持无须显示初始化而定义对象,这样的类提供一个合适的默认值,例如string类规定如果没有指定初值则生成一个空串。
string empty;
Sales_item item;
但一些类要求每个对象都显示初始化,此时如果创建了一个该类的对象而未对其做明确的初始化操作,将引发其错误。
标识符的长度没有限制,但对大小写敏感。用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。此外,定义在函数体外的标识符不能以下划线开头。变量名一般用小写字母,用户自定义的类名一般以大写字母开头,如果标识符由多个单词组成,则单词之间应有明显的区分(如student_loan
, studentLoan
)。
作用域操作符:
::result
//作用域操作符的左侧为空,向全局作用域发出请求获取作用域操作符右侧名字对应的变量(全局变量)。
但如果函数有可能用到某个全局变量,则不宜再定义一个同名的局部变量。
复合类型:指基于其他类型定义的类型。(引用,指针,数组)
引用定义时必须被初始
int &refVal = ival; //&refVal引用声明,&声明符,refVal 指向ival(是ival的另一个名字);
引用一旦初始化,将和它的初始值一直绑定在一起,无法令引用重新绑定到另外一个对象,因此引用时必须初始化。
引用并非对象,而是一个已经存在的对象所起的另外一个名字,定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用(初始化变量是拷贝)。为引用赋值,实际上是把值赋给了与引用绑定的对象。
因为引用本身不是一个对象,所以不能定义引用的引用。
引用只能绑定到对象上,而不能与某个字面值或者表达式的计算结果绑在一起。
引用的类型必须与其引用对象的类型一致。(例外:初始化const
引用时允许用任意表达式作为初始值——生成临时量对象,即引用一个非const
对象)
指针 指向另外一种类型的符合类型,实现了对其他对象的简介访问。
指针本身是一个对象。给指针赋值就是令它存放一个新的地址,从而指向一个新的对象。不能把变量直接赋给指针。
因为引用不是对象,没有实际地址,所以不能定义指向引用的指针。
用指针获取对象的地址(取地址符——操作符&
),用解引用符(操作符*
)来访问该对象:
int ival=42;
int *p=&ival; //*p声明, 等同于int *p; p = &ival; p是指向变量ival的指针(ival地址);
cout<<*p; //* 解引用符,*p是指针p所指的对象(ival本身值)。
*p=0; //为*p赋值实际上是为p所指对象ival赋值;* 解引用符
int ival = 42;
int *p= &ival;//p是指向ival的地址,*p表示该地址的内容(值)//等同于int *p; p = &ival;
int **q;
q = &p;//q是指向ival指针的指针,即q是p的指针,指向p的地址
//int *q;
//q = p;
cout << *p << " " << p << " " << &p << endl; //*p是ival的值,p是ival的地址,
cout << **q<< " " << *q << " " << q << endl;//**q(ival的值)是*q的值,*q(ival的地址)是q值,q(ival的地址)是p取地址p = &ival;
空指针 三种形式:
int *p1=nullptr; //特殊类型的字面值,可以转化为任意其他的指针类型。
int *p2=0;
int *p3=NULL;(需#include cstdlib)
对于两个相同类型的合法指针,可以比较,指针相等(存放的地址值相同)的情况有三种:都为空;都指向同一个对象;都指向同一对象的下一地址。 当一个指向一个对象,另一个指向另一个对象的下一地址时,也有可能相等。
void*
是一种特殊的指针类型,可用于存放任意对象的地址。但无法访问所存的对象,因为不知道是什么类型,不能确定能对这个对象做哪些操作。
面对一条比较复杂的指针或引用语句时,从右往左阅读(前面都是修饰语,最后一个词才是主语):
int i=42;
int *p;
int *&r=p;//r是一个int指针类型的引用。
r=&i;//给p赋值,指向i
const
关键字(限定符)——常量
const
对象必须初始化,并且一旦创建后其值就不能再改变。
const int i=get_size(); //运行时初始化
const int j=1; //编译时初始化
默认情况下,const
对象仅在文件内有效,如果要在其他多个文件中使用,必须在定义和声明前面都加上extern
关键字。
extern const int i=get_size();
extern const int i;
对const
的引用不能改变其引用对象的值。
const int ci=50;
const int &r1=ci;
const
引用/指向一个非const
对象(非const
不能引用/指向一个const
对象),引用/指针本身不可以改变其引用/指向对象的值,但是可以通过其他途径改变引用/指向对象的值。
指向常量的指针(底层):指针指向常量对象,该对象的值不能被改变,指针指向(地址)可以改变。
const int *ptr;
常量指针(顶层):指针本身是常量,一旦初始化完成,就不能再指向其他对象(指针所指向地址不可改变),而可以改变那个对象的值。
int *const ptr;
一般来说,非常量可以转化为常量,反之不行。
constexpr
由编译器来验证变量是否是一个常量表达式(指值不会改变并且在编译过程中就能得到计算结果的表达式
constexpr
指针初始值必须是nullptr
或0
或存储于某个固定地址中的对象
constexpr
修饰指针会把他所定义的对象置为顶层const: constexpr int *p=nullptr;//p常量指针
字面值类型 算数类型、指针、引用、某些类都是
类型别名
typedef double wages; //wages 是double的
using SI=Sales_item; //SI是Sales_item的别名
typedef char *pstring;
const pstring cstr=0; //cstr是 指向char变量的常量指针
auto
(编译器分析表达式所属的类型)、decltype
(选择并返回操作数的数据类型)(引用从来都作为其所指对象的同义词出现,只有用在decltype
处是一个例外,decltype(r)
是一个引用类型,decltype(*p)
的结果类型是int&
)string
是表示可变长的字符序列,vector
存放的是某种给定类型对象的可变长序列。
直接初始化(string s2(“abcd”))
比拷贝初始化(string s2=”abcd”)
可读性更好
string s3(10,’c’); //直接初始化,连续10个c
在执行读取操作时(输入输出),string
对象会自动忽略开头的空白(空格符、换行符、制表符等),并从第一个真正的字符开始读起,直到遇见下一处空白为止。
输入:” hello world! ”
输出:”hello”
getline(cin/is, str_line);`//输入一整行,遇到换行符结束(读取时丢弃换行符),可以输入空格
`string.empty();`//返回ture or false,是否为空
`string.size();`//返回字符的个数,返回值是string::size_type类型,无符号整形数。下标也对应size_type类型。
两个string对象相加,允许把字符字面值和字符串字面值转化为string
对象,但必须确保每个加法运算符(+)
两侧的运算对象至少有一个是string
如:
string s6=s1+”,”+”world”; //正确
string s6=”,”+”world”+s1; //错误
处理string对象中的字符
使用基于范围的for语句:
string str(“abcd”); for(auto c:str); cout <
ispunct(c);//该字符是否是标点符号
toupper(c); //转换为大写字母
isspace(c);//是否为空
Page82 表3.3 cctype头文件中的函数
支持迭代器,要习惯用“!=”,iter.begin()!=iter.end();
//保证字符串不为空,iter.end()
元素不存在,尾后迭代器
如果不清楚元素的确切个数,请使用vector
。
vector
表示对象的集合,其中所有对象的类型都相同。
vector
不包含引用的vector,引用不是对象。此外,大多数内置类型和类类型都可以构成vector对象,甚至组成vector元素的也可以是vector,如:vector
使用拷贝初始化时(=)
,只能提供一个初始值;如果提供的是一个类内初始值,则只能使用拷贝初始化或使用花括号的形式初始化;如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化。
vector
更适合先创建一个空vector(默认初始化),然后用成员函数push_back()
向其添加元素。Vector对象能高效增长。
范围for语句体不应改变其所遍历序列的大小。如果循环体内包含有向vector对象添加元素的语句,则不能使用范围for循环,即不能再范围for循环中向vector对象添加元素。
v.size();
//返回字符的个数,返回值是vector
类型,无符号整形数。下标也对应size_type
类型。
但凡是使用了迭代器的循环体,都不要向迭代器所述的容器添加元素。
箭头运算符(->)
把解引用和成员访问两个操作结合在一起,(it*).empty(); = it->empty();
把迭代器vector
转化为int
型,iter-iter.begin();
//difference_type的带符号整形数。
支持迭代器,iter.begin()
是迭代器,类型是vector
,可读可写;vector
是常量类型迭代器,只能读不能写
数组a[b]
,a是数组名,b是维度,维度必须大于0,必须是常量表达式。默认情况下,数组元素被默认初始化。(函数内部定义,默认初始化会令数组含有未定义的值)
定义数组时必须指定类型,不允许使用auto
推断类型。数组的元素为对象,也不存在引用作为元素。
初始化时,可以用列表初始化,列表元素小于维度的,剩余的默认初始化;列表元素不能大于维度;如果用列表初始化,维度可以为空,编译器自动计算维度大小。如:int a[5]={3,2,4};
注意:字符串字面值数组(也叫C风格字符串)的结尾处有一个空字符,字符串字面值用于初始化char
数组的时候,空字符占据一个位置,如:
char a3[]=”abc”;//占用4个维度,\0占一个维度,与string a3=”abc”不同,后者没有‘\0’
char a2[]={‘a’,‘b’,‘c’,‘\0’};//列表初始化,结尾处无空字符,若写出显示空字符也占用一个维度;
C风格字符串包含的函数都要使用在有空字符结束的情况下,否则错误:strlen(a3)
(返回长度,空字符不计算在内)、strcmp(p1,p2)
、strcat(p1,p2)
、strcpy(p1,p2)
。对大多数应用来说,使用标准库string
要比使用C风格字符串更安全更高效。
数组不允许将内容拷贝给其他数组和为其他数组赋值。
理解数组的含义原则:先由内到外,再由右到左,如下:
int *p[10];//含有10个元素的数组,含有10个int指针元素的一个数组;
int &p[10];//错误,不存在引用的数组
int (*p)[10];//指针,指针指向一个含有10个int型元素的数组
int *(&p)[10];//引用,含有10个元素的数组,含有10个int型指针元素的数组的引用
数组元素使用范围for语句或下标运算符访问。数组下标定义为size_t类型(无符号类型,足够大)。
内置的下标运算符所用的索引值不是无符号类型,可以处理负值,如下:
int a[10];//默认初始化为0
int *p=&a[2]; //p指向索引为2的元素;等价于int *p=a+2;
int k=p[-2];//p[-2]是a[0]表示的那个元素
指针也是迭代器:
int *beg=begin(a);//指向a首元素地址,等价于int *beg=a;begin(),end()函数头文件iterator。
int *end=end(a);//指向a尾元素地址的下一位置的指针(尾后指针),等价于int *end=&a[10];
while(beg != end &&*beg>=0) ++beg; //寻找第一个负值
特别注意,尾后指针不能执行解引用操作和递增操作。
两个指针相减的结果的类型是ptrdiff_t
(带符号类型),差值可能为负。空指针相减为0.
允许指针加上或者减去一个常量表达式,表示前进了或者后退了该整数值个位置。
可以混用string对象
和C风格字符串
(也可+),以空字符结束的字符数组可以赋值给string对象,但反过来赋值需要用c-str()函数,如:
const char *str = s.c_str();//返回值是一个C风格字符串;
可以用数组初始化vector;
vector
但建议避免使用内置数组和指针,尽量使用vector
和迭代器;避免使用C
风格字符串,尽量使用string
。
多维数组
多维数组指的是数组的数组
要使用范围for语句
处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型,避免转换成该数组内首元素的指针。
指针占四个字节,char占一个字节,int占4个字节,虚函数需要一个指针占4个字节,类A继承类B类A=类B所占字节+类B本身,int和short要考虑对齐。
重载运算符??
求值顺序:有四种运算明确规定了运算对象的求值顺序。逻辑与(&&
)、逻辑或(||
)、条件(?:
)、逗号(,
)运算符
布尔类型
的运算对象将被提升为int
类型;
运算符%
(取余或取模)的运算对象必须是整数,如果m%n
不等于0,则结果符号与m相同;
整数相除若商含有小数,直接弃除(一律向0取整);
混合运算对象的基本数据类型,小整形一般会提升为较大的整数类型。
逻辑运算符(逻辑与或非)和关系运算符的返回值都是bool类型
逻辑与和逻辑或都为短路求值,运算符对象左侧为真(后者为假)时才对右侧运算对象求值。
关系运算符(>、<、>=、<=、==、!=
)都满足左结合律,返回布尔值。
赋值运算符(=
)满足右结合律。赋值运算符优先级较低,低于关系运算符,所以一般赋值要加括号。
建议:除非必须,否则不使用递增递减运算符的后置版本
int i=0, j;
j=++i; //前置版本,本身先自增1,再赋值给j;
j=i++; //后置版本,先赋值给j;再自增1.
解引用和递增混用:*iter++
,表示*(iter++)
,递增运算符优先级大于解引用,先输出当前位置的解引用值,再将位置往后移动一位。
成员访问运算符:解引用的优先级低于点运算符,(*p).size();=p->size();
条件运算符(?:
),可嵌套,嵌套层数最好别超过两到三层。
Grade=(garde>90)?”high pass”:(grade<60)?”fail”:”pass”;
//满足右结合律,从右向左
条件运算符的优先级非常低,通常要加括号
位运算符(~、<<、>>、&、^、|
),左结合律,强烈建议仅用于处理无符号类型。优先级不高不低。
sizeof
运算符 右结合律 ,返回值是size_t
类型,返回一条表达式或一个类型名字所占的字节数(空间大小)。
szeof (type)
sizeof expr //返回表达式结果类型的大小
sizeof(ia)/sizeof(*ia);
//返回ia元素的数量逗号运算符 ?? for(int i=0; i != a.size(); ++i , --(a.size()))
类型转换——隐式转换:编译器自动执行
int
小的整型提升为较大的整数类型;bool、char、signed char、unsigned char、short、unsigned short→int
if
、while
) 类型转换——显示转换
命名的强制类型转换:static_cast、dynamic_cast、const_cast、reinterpret_cast
(建议:避免强制类型转换)
旧式的强制类型转换
运算符优先级表(Page 147)??
While
或for
的循环体必须是一条语句(以分号结束),所以用花括号括起来转换成一条复合语句(也称块,一个块就是一个作用域,块不以分号结束)
空块等价于空语句。
if
分支多于else
分支时,else
与离它最近的尚未匹配的if
匹配。要想使else
分支与外层的if语句匹配,可以在内层if
语句的两端加上花括号,使其成为一个块。
if(a>b)
if(x>y)
a=b;
//else与内层if匹配
else
a=a;
if(a>b){
if(x>y)
a=b;
}//else与外层if匹配
else
a=a;
switch()
语句中,case
标签(case
关键字和对应的值)必须是整型常量表达式。还可以省略break
以使得多个case
标签对应同一个操作。
char ch;
int i;
cin<switch(ch){
case ‘a’:
case ‘b’:
++i;
break;
case ‘c’:
--i;
break;
default: //default标签,没有任何case标签能够匹配的情况下,执行该语句,其目的在于,烤熟读者,考虑到了默认的情况。
;
}
迭代语句(循环)
定义在while
条件部分或者while
循环体内的变量每次迭代都经历从创建到销毁的过程。选择while
的情况:不知道需要迭代多少次;想在循环结束后访问循环控制变量。
for
语句头中定义的对象只在for
循环体内可见,for
循环结束后就不可用了。for
语句头中可以多重定义,如 for(int i=0, sz=a.size(); i != sz; ++i)
。
范围for
语句来源于与之等价的传统for
语句,不能在其内增加vector
对象(:右侧表达式)的元素。
do while
语句:先执行循环体,后检查条件,至少执行一次循环体;不允许在条件部分定义变量。
跳转语句 (break、continue、goto、return
)
break
语句负责终止离它最近的while
、do while
、for
或swicth
语句(只能出现在他们内部),跳出循环或选择,并从这些语句之后的第一条语句开始继续执行。
continue
语句终止最近的循环中的当前迭代并开始立即开始下一次迭代(终端当前迭代继续下次迭代)。
goto
语句的作用是从goto
语句无条件的跳转到同一函数内的另外一条语句。
默认实参 对函数定义的形参在形参列表中进行初始化。
定义:
string screen(int ht=24, char background=’’);
一旦某个形参被赋予了默认值,它后面的所有形参都必须有默认值。调用函数时,实参为空(省略实参),就调用默认实参,不省略就重新赋值给形参,多个实参可部分省略尾部的实参。
一个函数只声明一次,但多次声明同一个函数也是合法的,不过给定的作用域中一个形参只能被赋予一次默认值,函数的后续声明只能为之前的那些没有默认值的形参的添加默认实参。如:
声明:
string screen(int , char=’’);
string screen(int =24, char);
局部变量不能作为默认实参。
内联函数 在函数返回值前加上关键字inline
内联函数可避免函数调用的开销,用于优化规模较小、流程直接、频繁调用的函数
inline const string &shorterString(const string &s1,const string &s2)
{
return s1.size()<=s2.size()?s1:s2;
}
编译过程中调用函数时相当于展开成s1.size()<=s2.size()?s1:s2;
在C++
中,在类的内部定义了函数体的函数,被默认为是内联函数(隐式内联)。而不管你是否有inline关键字(显示内联)。
constexpr
函数 只能用于常量表达式的函数。函数的返回类型及所有形参的类型都得是字面值类型。函数体中必须有且只有一条return
语句,也可包含其他语句(不执行任何操作的语句,空语句,类型别名,using声明)。该语句被隐式地指定为内联函数。
constexpr
函数不一定返回常量表达式。
把内联函数和constexpr
函数定义放在头文件内。可多次定义,但多个定义必须完全一致,故通常定义在头文件中。
调试帮助(未看) assert
和NDEBUG
函数匹配
确定候选函数和可行函数
寻找最佳匹配(如果有的话)
含有多个形参的函数匹配
调用重载函数应尽量避免强制类型转换
函数指针 函数指针指向函数而非对象
声明函数指针:只需要用指针替换函数名,指针要加括号(否则是指针函数),如
bool lengthCompare(const string&, const string&);
bool (*pf)(const string&, const string&);
把函数名当做一个值使用时,该函数自动的转换为指针
pf=lengthCompare;//pf指向名为lengthCompare的函数
pf=&lengthCompare;//与上句等价
bool b1=pf(“hello”,“goodbye”);//函数调用
bool b1=(*pf)(“hello”,“goodbye”);//等价上句
bool b1=lengthCompare(“hello”,“goodbye”);//等价上句
形参可以是指向函数的指针。
构造函数不能包含返回语句。
默认构造函数:通过该函数控制默认初始化过程。
Sales_data()=defult;
//定义该函数目的是需要默认构造函数,也需要其他形式的构造函数。
//显示的声明需要默认的构造函数
常量成员函数: 把const关键字放在成员函数的参数列表之后,紧跟在函数参数列表之后的const表示this是一个指向常量的指针。
std::string isbn() const {return this->bookNo;}//this 可省略
只能在一个常量对象上调用const
成员函数。
在C++
中,在类的内部定义了函数体的函数,被默认为是内联函数。而不管你是否有inline
关键字。
只有当类没有什么任何构造函数时,编译器才回自动生成默认构造函数。
使用this
来把对象当成一个整体访问,而非直接访问对象的某个成员。
尽管编译器能合成拷贝、赋值和销毁的操作,但对于某些类来说合成的版本无法正常的工作。特别是,当类需要分配类对象之外的资源时,合成的版本常常会失效。
使用vector
或者string
的类能避免分配和释放内存带来的复杂性。
访问说明符 加强类的封装性。
定义在public
说明符之后的成员在整个程序内可被访问,public
成员定义类的接口。
定义在private
说明符之后的成员可以被类的成员函数访问,但是不能别使用该类的代码访问,private
部分封装了(即隐藏了)类的实现细节。
使用class
和struct
定义类唯一的区别就是默认的访问权限不同:使用class
关键字,定义在第一个访问说明符之前(默认)的成员是private的;使用struct
则是public。
友元和public区别?友元破坏原有的封装性和隐藏性。可以在public中写一个get函数get要用的私有成员(return)。
char get const {return contents[cursor]}
友元:声明友元后,类允许其他类或者函数访问它的非公有成员。
友元的声明声明,增加一条以friend
关键字开始的函数声明(友元声明只能出现在类定义的内部,具体位置不限,一般最好在类定义开始或者结束前的位置集中声明)。在友元声明之外要再专门对函数进行一次独立的声明(非成员接口函数,定义在类外,同一个头文件中)。
类型成员 类自定义某种类型在类中的别名。如:typedef std::string::size_type;
通常出现在类开始的地方。
可变数据成员 在变量的声明中对类的某个数据成员加入mutable
关键字,任何成员函数,包括const
成员函数在内都能改变它的值。
当我们提供一个类内初始值时,必须以=
或花括号表示。
一个const
成员函数如果以引用的形式返回*this
,那么他的返回类型将是常量引用。???
即使两个类的成员完全一样,他们也是不同的类型。
友元关系不存在传递性,不能传递友元的友元。
函数的返回类型通常出现在函数名之前,因此当成员函数定义在类的外部时,返回类型中使用的名字都位于类的作用域之外,这时,返回类型必须指明它是哪个类的成员。Page 253
在类中,构造函数和析构函数不占内存。指针占四个字节,char
占一个字节,int
占4个字节,虚函数需要一个指针占4个字节,类A继承类B,类A=类B所占字节+类B本身,int
和short
要考虑对齐。
编译器处理完类中的全部声明后才会处理成员函数的定义。
在类中,如果成员使用了外层作用域中的某个名字,而该名字代表一种类型,则类不能在之后重新定义该名字。
构造函数的初始值有时必不可少 如果成员是const、引用或者属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始列表为这些成员初始化。
class ConstRef{
public:
ConstRef(int ii);
private:
int i;
const int ci;
int &ri;
};
ConstRef::ConstRef(int ii):i(ii),ci(ii),ri(i) {}
委托构造函数:当一个构造函数委托给另一个构造函数时,受委托的构造函数的初始值列表和函数体依次被执行。
explicit 关键字 ???
聚合类 用户可直接访问其成员
所有成员都是public
的;
没有定义任何的构造函数;
没有类内初始值;
没有基类,也没有virtual
函数。
用户可以直接在花括号内按照声明顺序初始化类内成员。
constexpr
构造函数的函数体一般是空的。一个字面值常量类必须至少提供一个constexpr构造函数。constexpr构造函数必须初始化所有数据成员,初始值或者是constexpr构造函数或者是一条常量表达式。
类的静态成员 在声明之前加上static
关键字,独立于任何类的对象之外,不是由类的构造函数初始化的。
一般来说,不能在类的内部初始化静态成员。在static
后加上constexpr
可以内类初始化,之后不能再指定一个初始值了。
静态成员函数不能声明为const
的,也不能再其内使用this
指针。
可用作用域运算符直接访问静态成员,也可用类的对象、引用或者指针来访问静态成员。(成员函数不用通过作用域运算符直接使用静态成员)
static
关键字只出现在类内部的声明语句当中。
静态数据成员的类型可以就是它所属的类类型。而非静态数据成员则受到限制,只能声明成它所属类的指针或引用。
静态成员可以作为默认实参(给函数赋初值)。