注:本篇内容全部为选修内容
练习题答案
选修部分
//1.
enum Name{
John,Alice,Ender
};
//or
enum class Name{ //C++11
John,Alice,Ender
};
//2.
struct Rect{
double a,b;
};
表达式
什么是表达式?
我们可以这么说:
- 一个变量(或常量)是一个表达式
例如:1(1是const int型变量/int型常量)和a(a必须是一个变量) - 由一个运算符以及与几个子表达式按照特定方法排列的是一个表达式
如:1+2
(运算符:’+’,子表达式:1和2)和a<0?-a:a
(运算符’?:’,子表达式a<0
,-a
和a
)
那么,运算符又有哪些呢?
下面是C++运算符表:
运算符 | 描述 |
---|---|
优先级1 | |
:: | 给标识符加上作用域前缀,例如std::cin |
优先级2 | |
() | 函数调用,例如abs(1) 和min(3,4) |
[] | 获取数组元素,例如arr[0] |
-> | 获取指针指向对象的成员,例如p->get() ,对于指针而言(p为指针),其等价于(*p).get() |
. | 获取对象的成员,例如obj.get() |
++ | 后缀版。将变量自加1后返回自加前的值。例如:a++ |
-- |
后缀版。将变量自减1后返回自减前的值。例如:a-- |
const_cast | 用于类型转化。本系列不会介绍 |
dynamic_cast | 用于类型转化。本系列不会介绍 |
static_cast | 用于类型转化。本系列不会介绍 |
reinterpret_cast | 用于类型转化。本系列不会介绍 |
typeid | 用于动态获取类型。本系列不会介绍 |
优先级3 | 自右向左结合(例如,!++a 等价于!(++a) ) |
! | true(非零值)变成false(0),false变成true。例如!a |
~ | 按位非运算(例如01101100变成10010011),例如~a |
++ | 前缀版。将变量自加1后返回自加后的值。例如:++a |
-- |
前缀版。将变量自减1后返回自减后的值。例如:--a |
- | 负号。例如:-1 |
+ | 正号。例如:+2 |
* | 解除引用。*p 即p所指向的变量 |
& | 获取变量地址。例如&a |
new | 用于动态分配内存。例如new int (详见本篇后面的动态内存分配) |
new[] | 用于动态分配内存。例如new int[5] |
delete | 用于释放new分配的内存。例如delete p |
delete[] | 用于释放new[] 分配的内存。例如delete[] p |
(type) | 用于类型转换。例如(double)1 |
sizeof | 用于获取类型和变量所占内存的大小。例如:sizeof(int) |
优先级4 | |
.* | 用于成员指针(关于成员指针的内容,会在讲完动态内存分配后简单讲一下)。例如:对于int type::a=&type::m 则可以有obj.*a |
->* | 用于成员指针。例如:p->*a |
优先级5 | 从左向右结合(例如a*b%c 等价于(a*b)%c ) |
* | 乘法。例如2*3 |
/ | 除法。注意,对于两个整数,其结果仍是整数(余数舍弃)。例如8/5 等于1 |
% | 求余数。仅用于整型。其中a%b 等价于a-a/b*b |
优先级6 | 从左向右结合 |
+ | 加号。例如a+b |
- | 减号。例如6-4 |
优先级7 | 从左向右结合 |
<< | 左移号。例如233<<1 等于466(因为233转换成二进制是11101011,而111010110转换成十进制是466) |
>> | 右移号。例如1417763>>16 (==21) |
优先级8 | 从左向右结合 |
< | 小于号,例如a<b |
<= | 小于等于号,例如a<=b |
> | 大于号,例如a>b |
>= | 大于等于号,例如a>=b |
优先级9 | 从左向右结合 |
== | 等于号(注意不要和赋值号’=’弄混)。例如a==b |
!= | 不等号。例如a!=b |
优先级10 | 从左向右结合 |
& | 按位与。只有1与1为1,其余为0。例如,233&123 等于105(233是11101001,123是01111011,01101001是105) |
优先级11 | 从左向右结合 |
^ | 按位异或。不同为1,其余为0。例如,233^123 等于146(233是11101001,123是01111011,10010010是146) |
优先级12 | 从左向右结合 |
| | 按位或。只有0或0为0,其余为1。例如,233|123 等于251(233是11101001,123是01111011,11111011是251) |
优先级13 | 从左向右结合 |
&& | 逻辑与。只有true与true为true,其余为false。例如,true&&false 等于false |
优先级14 | 从左向右结合 |
|| | 逻辑或。只有false或false为false,其余为true。例如,true||false 等于false |
优先级15 | 从右向左结合 |
?: | 条件判断。第一个表达式若为true则计算并返回第二个表达式的值,反之则第三个。例如a<b?b:a |
优先级16 | 从右向左结合 |
= | 赋值号。例如a=5 |
+= | a+=b 等价于a=a+b |
-= | a+=b 等价于a=a-b |
*= | a+=b 等价于a=a*b |
/= | a+=b 等价于a=a/b |
%= | a+=b 等价于a=a%b |
&= | a+=b 等价于a=a&b |
^= | a+=b 等价于a=a^b |
|= | a+=b 等价于a=a|b |
<<= | a+=b 等价于a=a<<b |
>>= | a+=b 等价于a=a>>b |
优先级17 | |
throw | 抛出异常。我们之后会有专门的章节介绍异常机制 |
优先级18 | 从左向右结合 |
, | 先计算第一个表达式,在计算第二个表达式。返回第二个表达式的值。例如i++,j++ |
动态内存分配
当你需要做一些事情时,你可能会向计算机帮忙。比如,讲几个整数排个序之类的。
这时你需要一个数组。但问题是,数组开多大呢?
//sort1.cpp
#include<iostream>
#include<algorithm> //for function soft
using namespace std;
int arr[10];
int main(){
int n;
cout<<"Enter the number of integers:";
cin>>n;
cout<<"Enter them:\n";
for(int i=0;i<n;i++)
cin>>arr[i];
sort(arr,arr+n); //sort用来排序,对arr[0]到arr[n-1]进行排序
cout<<"After sorting:\n";
for(int i=0;i<n;i++)
cout<<arr[i]<<' ';
cout<<endl;
return 0;
}
这里涉及了一些新的内容。循环语句for会在下一篇讲解,而函数sort会在讲STL的时候讲解
不妨来看一下输入输出(加粗内容为输入):
Enter the number of integers:5
Enter them:
4 2 5 4 8
After sorting:
2 4 4 5 8
似乎无懈可击,不是吗?
但如果,你要用15个数字排序呢?
注意一下数组长度只有10
可能不会有任何问题。也可能会出现错误。因为arr[10]~arr[14]是不安全的——你不知道这一块内存是干什么的。也许在这里有一个指针,然后你对这个指针指向的内容进行了修改。然后,就可能发生了你不愿意看到的事情。
或者,高明点的操作系统会阻止这些愚蠢且不安全的内存修改
数组得开大点,不是吗?
那开多大呢?
20?50?100?
呸!不是在谈钱。
但是,如果你,你需要给110个数字排序呢?
别说不可能。如果这个应用是给别人用的,就更有可能了。
当然,你也可以开个一百万。
但是,这样会占用过多的内存。而事实上,你可能最后只用了其中的10个变量。
静态内存分配就会有这样的弊端。就好像旅游,你想去参观某个景点,结果当天天气恶劣飞机走不了,第二天再去,计划全被打乱了。
所以有时我们要学会随遇而安——例如使用动态内存分配:
//sort2.cpp
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int n,*arr;
cout<<"Enter the number of integers:";
cin>>n;
arr=new int[n]; //分配内存
cout<<"Enter them:\n";
for(int i=0;i<n;i++)
cin>>arr[i];
sort(arr,arr+n);
cout<<"After sorting:\n";
for(int i=0;i<n;i++)
cout<<arr[i]<<' ';
cout<<endl;
delete[] arr; //释放分配的内存
return 0;
}
应该养成delete释放每一块由new分配的内存。尽管程序结束时操作系统会将其自动回收。但是,在程序结束前(如果你不delete),它必须一直放在那里,这种现象称之为内存泄露,过多的内存泄露可能会导致找不到足够的内存以运行程序,导致一个神奇的Bug
而且它极难跟踪。
哦,当然,正如您在”运算符”部分所看到的,与动态内存分配的运算符有4个:
- new
- new[]
- delete
- delete[]
其中,new用于动态分配存储一个变量的内存,而new[]用于动态分配存储几个变量(类似于一个数组)的内存。
然后,还有几点想说:
- delete只能释放new的内存,delete[]只能释放new[]的内存,不能混用。
- 不要将已经被释放了的内存再释放一遍
- 不要将不是动态分配的内存释放
- 无论是delete还是delete[],空指针(NULL或0,以及C++11的nullptr)都是安全的。无论你释放几次
关于成员指针
哦,其实这个东西我之前也不知道,也是在运算符表中看到了那两个神奇运算符.*和->*才知道C++还有这么个东西。
好吧简单介绍一下
成员指针语法:
type strc::* p1;
type strc::* p2=nullptr; //可以为空指针
type strc::* p3=&strc::member; //也可以指向结构体,共用体和类的非静态成员(关于静态成员会在讲类的时候提及)
例如:
struct Strc{
int m_1,m_2;
double m_3,m_4;
static int m_static;
};
int Strc::* p1=nullptr; //or NULL for C++98
int Strc::* p2=&Strc::m_2;
double Strc::* p3=&Strc::m_3;
Strc strc;
strc.m_2=2;
strc.m_3=3;
cout<<strc.*p2<<endl; //strc.*p2==strc.m_2==2
cout<<strc.*p3<<endl; //strc.*p3==strc.m_3==3.0
cout<<strc.*p1<<endl; //不安全,结果也是未知的。例如我尝试的结果是-507135232
int Strc::* p4=&Strc::m_static; //error: cannot convert ‘int*’ to ‘int Strc::*’ in initialization
练习题
- 已知平方根函数为sqrt,位于头文件cmath中。请你编写一个程序,使其可以解一元二次方程
在下一篇,我们会将C++的条件和循环语句