- C++融合了3种不同的编程方式:C语言代表的过程性语言,C++在C语言基础上添加的类代表的面向对象语言(oop),C++模板支持的泛型编程。
- C++11初始化方式
//大括号初始化,等号可以使用,也可以不使用
int emus{7};
int rheas={12};
//大括号内不包含任何东西,这种情况下变量将被初始化为零
int rocs={};
int psychics{};
- C++存储整型的方式类似一个圈,负数存的是补码。可以手动模拟一下。
- 第一位为1-9,则基数为10(十进制),第一位为0,则基数为8(8进制),前两位为0x或0X,则基数为16
- cout的一些特殊特性
/*
cout提供了控制符dec,hex,oct分别用于指示cout以十进制,
十六进制和八进制格式显示整数。
*/
int tmp=42;
cout<<tmp<<endl;
cout<<hex;
cout<<tmp<<endl;
cout<<oct;
cout<<tmp<<endl;
- C++11强制类型转换运算符static_cast < typeName>(value)
//使用要求更为严格,虽然不知道有什么用。。
char c='c';
cout<<static_cast<int>(c)<<endl;
- cin.get() cin会留下换行符 cin.getline 不会留下换行符
- C++11新增的另一种类型:原始(raw)字符串
cout<<R"(Jim "King" Tutt uses "\n" instead of endl.)"<<'\n';
//输出 Jim "King" Tutt uses \n instead of endl.
- C++结构体种的位字段
C++允许指定占用特定位数的结构成员,这使得创建与某个硬件设备上的寄存器对应的数据结构非常方便。字段的类型应为整型或者枚举,接下来是冒号,冒号后面跟着一个数字,它制定了位数。可以使用没有名称的字段提供间距。每个成员都被称为位字段。
struct torgle_register
{
unsigned int SN : 4;
unsigned int : 4;
bool goodIn : 1;
bool goodTorgle : 1;
};
torgle_register tr = {14,true,false};
if(tr.goodIn)puts("Yes");
- 共用体
结构体可以同时存储int,long和double,共用体只能存储int,long或double
union one4all
{
int int_val;
long long_val;
double double_val;
};
one4all pail;
pail.int_val=15;
cout<<pail.int_val<<endl;
pail.double_val=1.38;
cout<<pail.double_val<<endl;
cout<<pail.int_val<<endl;
/*
输出
15
1.38
-515396076
*/
由于共用体每次只能存储一个值,因此它必须有足够的空间来存储最大的成员,所以,共用体的长度为其最大的成员的长度。
匿名共用体:
struct widget
{
char brand[20];
int type;
union
{
long id_num;
char id_char[20];
};
};
widget prize;
cin>>prize.type;
if(prize.type==1)cin>>prize.id_num;
else cin>>prize.id_char;
共用体常用于(但并非只能用于)节省内存。C++可用于嵌入式系统编程,如控制烤箱,MP3播放器或火星漫步者的处理器。对于这些应用程序来说,内存可能十分宝贵。另外,共用体常用于操作操作系统数据结构或硬件数据结构。
- sizeof运算符和指针,数组之间的关系。
char *p="abc";
cout<<sizeof(p)<<endl;//指针长度,一律返回4
cout<<sizeof(*p)<<endl;//字符长度,1个字节
char a[]="abcdef";
cout<<sizeof(a)<<endl;//数组长度 7
cout<<sizeof(&a)<<endl;//指针长度,一律返回4
cout<<sizeof(*a)<<endl;//字符长度,1个字节
- 类型组合(P118)
struct student
{
int year;
};
//变量
student a1,a2,a3;
a1.year=2003;
a2.year=2005;
//数组
student t[3];
t[0].year=2003;
(t+1)->year=2004;
//指针数组
const student *b[3]={&a1,&a2,&a3};
cout<<b[0]->year<<endl;
//数组指针
const student **c=b;
cout<<(*c)->year<<endl;
cout<<c[0]->year<<endl;
- 基于范围的for循环(C++11)(P152)
C++新增一种循环,基于范围(range-based)的for循环。
double prices[5]={4.99,1.99,6.87,7.99,8.49};
for(double x:prices)
cout<<x<<endl;
//要修改数组的元素,需要使用不同的循环变量语法
for(double &x:prices)
x=x*0.8;
//还可以使用基于范围的for循环和初始化列表
for(int x:{3,5,2,8,6})
cout<<x<<' ';
cout<<endl;
- 文件尾条件(P155)
输入来自于文件时,使用一些特殊符号来表示结束有时会很难令人满意,这时我们可以通过检测文件尾(EOF)。
如果输入来自于键盘呢,很多操作系统都允许通过键盘来模拟文件尾条件。在Unix中,可以在行首按下Ctrl+D来实现。在Windows命令提示符模式下,可以在任意位置按Ctr+Z和Enter。
如果编程环境检测到EOF后,cin将两位(eofbit和failbit)都置为1.可以通过成员函数eof()来查看eofbit是否被设置;如果监测到EOF,则cin.eof()将返回true,否则返回false。同样,如果eofbit或failbit被置为1,则fail()成员函数返回true,否则返回false。
cin.fail()比cin.eof()更通用,因为它可以检测到其他失败原因,如磁盘故障。 - cin.get()有两种用法,一种ch=cin.get(),一种cin.get(ch)。
- 写入及读取文件
//写入
ofstream outFile;
ofstream fout;
outFile.open("fish.txt");
char s[30];
scanf("%s",s);
fout.open(s);
outFile<<"hello world"<<endl;
outFile.close();
fout.close();
//读取
ifstream inFile;
//ifstream fin;
inFile.open("fish.txt");
if(!inFile.is_open())
{
puts("Error");
exit(EXIT_FAILURE);
}
char s[30];
while(inFile>>s)
{
cout<<s<<endl;
}
inFile.close();
//fin.close();
/*
fish.txt
hello world
*/
- 使用数组区间的函数(P220)
迭代器原型
int sum_arr(const int *begin,const int *end)
{
int tot=0;
for(const int *pt=begin;pt!=end;pt++)
tot=tot+*pt;
return tot;
}
int main()
{
sum[0]=1,sum[1]=2;sum[2]=3;
cout<<sum_arr(sum,sum+3);
}
- 指针与const(P222)
可以用两种不同的方式将const关键字用于指针,第一种方法是让指针指向一个常量对象,这样可以防止使用指针来修改所指向的值,第二种方法是将指针本身声明为常量,这样可以防止改变指针指向的位置。下面来看细节。
int age=30;
const int *pt=&age;
*pt += 1;//invalid
const int age2=30;
int *p2=&age2;//invalid
pt的什么并不意味着指向的值是一个常量,而只是意味着对pt而言,这个值是一个常量。我们可以通过age变量来修改age的值,但不能用pt指针来修改它。
第二种也不行。
如果指针指向指针,情况更复杂。
假设涉及的是一级间接关系,则将非const指针赋给const指针是可以的。
int age=39;
int *pd=&age;
const int *pt=pd;
但进入两级间接关系时,与一级间接关系一样将const和非const混合的指针赋值方式将不再安全,因此是不行的。如果允许这样做,则可以编写这样的代码:
const int **pp2;
int *p1;
const int n=13;
pp2=&p1;
*pp2=&n;
*p1=10;
上述代码将非const地址(&p1)赋给了const指针pp2,因此可以使用p1来修改const数据。因此,这样是不行的。
如果数据类型本身并不是指针,则可以将const数据或非const数据的地址赋给指向const的指针,但只能将非const数据的地址赋给非const指针。
- 函数指针
声明时函数指针的返回类型以及参数列表和函数必须一样。
double pam(int);
double (*pf)(int);
int main()
{
pf=pam;
double x=pam(4);
double y=(*pf)(5);
cout<<y<<endl;
y=pf(5);//这样写也行
cout<<y<<endl;
}
double pam(int a)
{
return 1.0*a*a;
}
深入探讨函数指针
假如现在有下面一些函数的原型,他们的参数列表和返回值类型相同:
const double *f1(const double ar[],int n);
const double *f2(const double [],int n);
const double *f3(const double *,int);
接下来,假设要声明一个指针,它可指向这三个函数之一。假定该指针名为p1:
const double *(*p1)(const double *,int);
p1=f1;
//还可以这样写
auto p2=f2;
现在来看下面的语句:
cout<<p1(a,3)<<' '<<*(p1(a,3))<<endl;
cout<<p2(a,3)<<' '<<*(p2(a,3))<<endl;
//前面返回的是地址,后面返回的是值
如果有一个函数指针数组,包含这三个函数,该怎么声明呢?
const double * (*pa[3])(const double *,int)={f1,f2,f3}
这里要说明一点,运算符[]的优先级高于*,其他的可以自己理解。
如果要声明一个函数指针数组指针,用来指向pa数组,该怎么声明呢?
//第一种
auto pc=&pa;
//第二种 pd是一个指针,它指向一个包含三个元素的数组。
const double *(*(*pd)[3])(const double *,int)=&pa;
(有点没弄清楚)这样的声明方式应该是数组指针的声明。
要调用函数,需认识到这一点,既然pd指向数组,那么*pd就是数组,而(*pd)[i]就是数组中的元素,即函数指针。因此,较简单的函数调用是(*pd)[i](av,3),而
∗
(
∗
p
d
)
[
i
]
(
a
v
,
3
)
\ast(\ast pd)[i](av,3)
∗(∗pd)[i](av,3)是返回的指针指向的值。也可以使用第二种使用指针调用函数的语法,使用
(
∗
(
∗
p
d
)
[
i
]
)
(
a
v
,
3
)
(\ast (\ast pd)[i])(av,3)
(∗(∗pd)[i])(av,3)来调用函数,而
∗
(
∗
(
∗
p
d
)
[
i
]
)
(
a
v
,
3
)
\ast (\ast (\ast pd)[i])(av,3)
∗(∗(∗pd)[i])(av,3)是指向的double的值。
请注意pa(它是数组名,表示地址)和&pa之间的差别。pa都是数组第一个元素的地址,即&pa[0]。因此,它是单个指针的地址。但&pa是整个数组(即三个指针块)的地址。从数字上说,pa和&pa的值相同,但它们的类型不同。一种差别是,pa+1为数组中下一个元素的地址,而&pa+1为数组pa后面一个12字节内存块的地址(假定一个地址为4个字节)。另一个差别是,要得到第一个元素的值,只需对pa解除一次引用,但需要对&pa解除两次引用:
∗
∗
&
p
a
=
=
∗
p
a
=
=
p
a
[
0
]
** \&pa==*pa==pa[0]
∗∗&pa==∗pa==pa[0]
使用typedef 进行简化
typedef 放在类型声明前,在变量的位置填上新类型的名字,这样新类型就可以当旧类型用,换句话说,声明变量时,在句首加上typedef,就相当于声明了新类型。
typedef const double *(*p_fun)(const double *,int);
p_fun p1=f1;
p_fun pa[3]={f1,f2,f3};
p_fun (*pd)[3]=&pa;
-
C++提供了许多新的函数特性,包括内联函数,按引用传递变量,默认的参数值,函数重载(多态)以及模板函数。
-
内联函数
内联函数的编译代码与其他的程序代码“内联”到一块,对于内联代码,程序无需跳到另一个位置处执行代码,再跳回来。因此,内联函数的运行速度比常规函数稍快,但代价是需要占用更多内存。
应有选择的使用内联函数。如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间只占一小部分。如果代码执行时间很短,则内联调用就可以节省非内联调用使用的大部分时间。 -
临时变量,引用参数和const(P262)
如果引用参数是const,则编译器将在下面两种情况下生成临时变量:- 实参的类型正确,但不是左值。
- 实参的类型不正确,但可以转换成正确的类型。
左值:左值参数是可被引用的数据对象,例如,变量,数组元素,结构成员,引用和解除引用的指针都是左值。非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的表达式。在C语言中,左值最初指的是可出现在赋值语句左边的实体,但这是引入关键字const之前的情况。现在,常规变量和const变量都可视为左值,因为可通过地址访问他们。但常规变量属于可修改的左值,而const变量属于不可修改的左值。
简而言之,如果接受引用参数的函数的意图是修改作为参数传递的变量,则创建临时变量将阻止这种意图的实现。解决方法是,禁止这么做。现在C++标准正是这样。
如果只是使用传递的值,而不是修改它们,因此临时变量不会造成任何不利的影响,反而会使函数在可处理的参数种类方面更通用。 -
继承的一个特征是,派生类继承了基类的方法(如ofstream继承了ostream,ofstream可以使用ostream的一些方法),另一个特征是,基类引用可以指向派生类对象,而无需进行强制类型转换。
-
C++如何跟踪每一个重载函数呢?C++编译器将执行一些神奇的操作-名称修饰或名称矫正。它会根据函数原型中指定的形参类型对每个函数名进行加密。