C++语法注意总结
字符串
我们常常遇到读取多行字符串,但这时候就有问题了.
举个例子:
#include <iostream>
int main
{
int a;
int name[20];
std::cin>>a;
//解决std::cin.get();把回车读入
std::cin.getline(name,20);
//这就出现一个严重的问题,cin是到换行符,制表符,回车停下.
//给变量a赋值后回车有下一行的cin.getline读入,约等于不执行.
}
指针和自由存储空间
要探究指针的本质,先看声明和初始化
eg:
int*p;
我们通常是(int*)p这么看的
为什么?为什么不可以int(*p)这样看?
我们说p 指向int 类型,我们还说p的类型是指向int的指针,
或int*.可以这样说,p是指针(地址),而*p是int,而不是指针
到后面,分为变量域与指针域
所以上面的二者意义分别为:
1.这强调*p是一个int类型的值
2.这强调的是:int*是一种类型—指向int的指针.在哪里添加空格对于编译器来说没有任何区别.
这里有个特别注意的点:
eg:
int a=23;
int *pe=&a;
其实,a与*pe
&a与pe等价
想一想*,&这2个符号的含义
还有:
new<-->delete加上指向内存块对应的指针//不建议使用
malloc<-->free
结构体中的指针
eg:
struct things
{
int good;
int bad;
};
things p1={3,4};//这里是一个结构
(things*)p2=&p1;//注意,我说的是p2这里是指向结构体的一个指针
很容易混这里:是前面举例的第二种类型
比如:3<-->p1.good;
4<-->p1.bad
如果要访问它们就要通过*得到*p2就是结构体p1本身再访问.
(*p1).good<==>p1->good
指针和const
将const用于指针有一些很微妙的地方(指针看起来总是很微妙),我们来详细探讨一下.可以用两种不
同的方式将 const 关键字用于指针.第一种方法是让指针指向一个常量对象,这样可以防止使用该指
针来修改所指向的值,第二种方法是将指针本身声明为常量,这样可以防止改变指针指向的位置.
int age=39;
const int*pt=&age;
pt指向一个const int(39),不能用pt来修改.换句话来说,*pt的
值为const,不能被修改
循环
小点:
++,–运算符运用
可以表现为:
a=20; b=20;
a++=20; ++b=21;
a=21; b=21;
粗略地讲,a++意味着使用a的当前值计算表达式,然后将a的值加1;而++b的意思是先将b的值加
1,然后使用新的值来计算表达式.
特别的,while还可以用作单片机开发中的延时函数
void time()
{
long wait=0;
while(wait<10000)
{
wait++;
}
}
重点:基于范围的for循环(C++11)
基于范围(range-based)的 for循环.这简化了一种常见的循环任务:对数组
(或容器类,如vector和array)的每个元素执行相同的操作,如下例所示:
double prices[5]={1,2,3,4,5};
for(double x:prices)
cout<<x<<std::endl;
其中,x最初表示数组prices 的第一个元素.显示第一个元素后,不断执行循环,而x依次表示数组
的其他元素.因此,上述代码显示全部5个元素,每个元素占据一行.总之,该循环显示数组中的每个值.
逻辑运算符
几个易混淆的运算符
逻辑NOT运算符–!
eg1:
int a=10;
if(!a)
{
a--;
}
else if(a)
{
a++;
}
这两个是不一样的,if类的语句判断括号对应语句内的是否为true,
(a)是否为不为0,!反过来为0
这在while(!a)中常见.
另外要注意优先级的问题:
!(x>5)和!x>5不一样后者通常不合法
(?:)–三目运算符
比如: 5>3?10:12 前者为true,后者取前,反之取后
eg2:
int c=a>b?a:b//int c=a; if(a>b) else c=b;
函数与结构
简单的举例:
struct tra_time
{
int hours;
int mins;
}
const int Min_per_hr=60;
tra_time sum(tra_time t1,tra_time t2)
{
tra_time total;
total.mins=(t1.mins+t2.mins)%Min_per_hr;
total.hours=t1.hours+t2.hours+(t1.mins+t2.mins)/Min_per_hr;
return total;与int等类型的函数返回值一样
}
void show_time(travel_time t)
{
using namespace std;
cout<<t.hours;
cout<<t.mins;
}
传递结构的地址(指针结合)
原:
void show_polar(polar dapos)
{
cout<<dapos.distance;
cout<<dapos.angle*Rad;
}
假设要传递结构的地址而不是整个结构以节省时间和空间,则需要重新编写前面的函数,使用指向结
构的指针.首先来看一看如何重新编写show_polar( )函数.需要修改三个地方:
调用函数时,将结构的地址(&pplace)而不是结构本身(pplace)传递给它:
将形参声明为指向polar的指针,即polar *类型.由于函数不应该修改结构,因此使用了const修饰符:
由于形参是指针而不是结构,因此应间接成员运算符(->),而不是成员运算符(句点).
完成上述修改后,该函数如下所示:
void show_polar(const polar*pda)
{
cout<<pad->distance;
cout<<pad->angle*Rad;
}
函数与指针
首先通过一个例子来阐释这一过程.假设要设计一个名为estimate( )的函数,估算编写指定行数的代码
所需的时间,并且希望不同的程序员都将使用该函数.对于所有的用户来说,estimate( )中一部分代码都是
相同的,但该函数允许每个程序员提供自己的算法来估算时间.为实现这种目标,采用的机制是,将程序
员要使用的算法函数的地址传递给estimate( ).为此,必须能够完成下面的工作:
获取函数的地址;
声明一个函数指针;
使用函数指针来调用函数.
1.获取函数的地址
获取函数的地址很简单:只要使用函数名(后面不跟参数)即可.也就是说,如果think( )是一个函数,
则 think 就是该函数的地址.要将函数作为参数进行传递,必须传递函数名.一定要区分传递的是函数的
地址还是函数的返回值:
process(think);
process(think());
process( )调用使得 process( )函数能够在其内部调用think( )函数.thought( )调用首先调用think( )函数,
然后将think( )的返回值传递给thought( )函数.
2.声明函数指针
声明指向某种数据类型的指针时,必须指定指针指向的类型.同样,声明指向函数的指针时,也必须
指定指针指向的函数类型.这意味着声明应指定函数的返回类型以及函数的特征标(参数列表).也就是说,
声明应像函数原型那样指出有关函数的信息.
举个例子:
函数原型:
double pam(int)
指针声明:
double (*pf)(int);
这与pam( )声明类似,这是将pam替换为了(*pf).由于pam是函数,因此(*pf)也是函数.而如果
(*pf)是函数,则pf就是函数指针.
提示:通常要声明指向特定类型的函数的指针,可以首先编写这种函数的原型,然后用(*pf)替换
函数名.这样pf就是这类函数的指针.
为提供正确的运算符优先级,必须在声明中使用括号将*pf括起.括号的优先级比*运算符高,因此*pf
(int)意味着pf( )是一个返回指针的函数,而(*pf)(int)意味着pf是一个指向函数的指针:
double (*pf)(int);
pf 是一个指针,指向一个函数,该函数接受一个 int 类型的参数,并返回一个 double 类型的值.
括号的使用 (*pf) 表明 * 作用于 pf,因此 pf 是一个指针,而不是函数返回指针的函数.
double *pf(int);
pf 是函数的名称,该函数接受一个 int 类型的参数.
这里的 * 表示函数返回的是一个指针,而不是 pf 本身是一个指针.
3.使用指针来调用函数
现在进入最后一步,即使用指针来调用被指向的函数.线索来自指针声明。前面讲过,(*pf)扮演的
角色与函数名相同,因此使用(*pf)时,只需将它看作函数名即可
引用变量:
C和C++ 使用&符号来指示变量的地址.C++给&符号赋予了另一个含义,将其用来声明引
用.例如,要将rodents作为rats变量的别名,可以这样做:
int rats;
int & rodents=rats;
其中,&不是地址运算符,而是类型标识符的一部分.就像声明中的char*指的是指向char的指针一样,
int &指的是指向int的引用.上述引用声明允许将rats和rodents互换—它们指向相同的值和内存单元.
本质上是变量只有一个,但名称却有多个.
引用的属性和特别之处
在函数中,引用的参数在函数运行的过程中本身的值会被修改(按引用传递)
举例:
int func1(int x)
{
x*=x;
return x;//传入的参数x并不会被改变.
}
int func2(int &x)
{
x*=x;
return x;//按引用传递,传入的参数本身会被修改
}
临时变量,引用参数和const
如果实参与引用参数不匹配, C++ 将生成临时变量.当前仅当参数为const 引用时,C++ 才允许这
样做,但以前不是这样.下面来看看何种情况下,C++将生成临时变量,以及为何对const引用的限制是
合理的.
首先,什么时候将创建临时变量呢?如果引用参数是const,则编译器将在下面两种情况下生成临时
变量:
实参的类型正确,但不是左值:
实参的类型不正确,但可以转换为正确的类型.
左值是什么呢?左值参数是可被引用的数据对象,例如,变量,数组元素,结构成员,引用和解除引
用的指针都是左值.非左值包括字面常量(用引号括起的字符串除外,它们由其地址表示)和包含多项的
表达式.在C语言中,左值最初指的是可出现在赋值语句左边的实体,但这是引入关键字const之前的情
况.现在,常规变量和 const 变量都可视为左值,因为可通过地址访问它们.但常规变量属于可修改的左
值,而const变量属于不可修改的左值.
注意:如果函数调用的参数不是左值或与相应的const 引用参数的类型不匹配,则C++将创建类型正
确的匿名变量,将函数调用的参数的值传递给该匿名变量,并让参数来引用该变量.
应尽可能使用const 将引用参数声明为常量数据的引用的理由有三个:
使用const可以避免无意中修改数据的编程错误
使用const使函数能够处理const和非const实参,否则将只能接受非const数据
使用const引用使函数能够正确生成并使用临时变量.
因此应尽可能将引用形参声明为const
C++11 新增了另一种引用—右值引用(rvalue reference)这种引用可指向右值,是使用&&声明的:
新增右值引用的主要目的是,让库设计人员能够提供有些操作的更有效实现.
int && r=std::sqrt(36);
std::cout<<r;//6
引用
结构引用
举个例子:
1 | struct free_throws |
类对象引用
将类对象传递给函数时,,C++通常的做法是使用引用.例如,可以通过使用引用,让函数将类string,ostream,istream,ofstream 和 ifstream 等类的对象作为参数.
看下面这三个函数:
1 | string version1(const string &s1,const string &s2) |
何时使用引用参数
使用引用参数的主要原因有两个. 程序员能够修改调用函数中的数据对象.
通过传递引用而不是整个数据对象,可以提高程序的运行速度. 当数据对象较大时(如结构和类对象).
第二个原因最重要.这些也是使用指针参数的原因.这是有道理的,因为引用参数实际上是基于指针的代码的另一个接口.那么,什么时候应使用引用,什么时候应使 用指针呢?什么时候应按值传递呢?下面是一些指导原则:对于使用传递的值而不作修改的函数.
如果数据对象很小,如内置数据类型或小型结构,则按值传递.
如果数据对象是数组,则使用指针,因为这是唯一的选择,并将指针声明为指向const的指针. 如果数据对象是较大的结构,则使用const指针或const引用,以提高程序的效率.这样可以节省 复制结构所需的时间和空间.
如果数据对象是类对象,则使用const 引用.类设计的语义常常要求使用引用,这是C++新增这项特性的主要原因.因此,传递类对象参数的标准方式是按引用传递.
对于修改调用函数中数据的函数: 如果数据对象是内置数据类型,则使用指针.
如果看到诸如fixit(&x)这样的代码(其中x是int), 则很明显,该函数将修改x. 如果数据对象是数组,则只能使用指针.
如果数据对象是结构,则使用引用或指针.
如果数据对象是类对象,则使用引用.
当然,这只是一些指导原则,很可能有充分的理由做出其他的选择.例如,对于基本类型,cin使用引用,因此可以使用cin>>n,而不是cin >> &n.
重载
指的是可以有多个同名的函数,因此对名称进行了重载.这两个术语指的是同一回事,但我们通常使用函数重载.可以通过函数重载来设计一 系列函数—它们完成相同的工作,但使用不同的参数列表.
重载函数就像是有多种含义的动词.例如,Piggy 小姐可以在棒球场为家乡球队助威(root),也可以 在地里种植(root)菌类作物.根据上下文可以知道在每一种情况下,root的含义是什么.同样,C++使用上下文来确定要使用的重载函数版本.
函数重载的关键是函数的参数列表—也称为函数特征标(function signature).如果两个函数的参数 数目和类型相同,同时参数的排列顺序也相同,则它们的特征标相同,而变量名是无关紧要的.C++允许定义名称相同的函数,条件是它们的特征标不同.如果参数数目和或参数类型不同,则特征标也不同.例如,可以定义一组原型如下的print( )函数:
编译器将根据所采取的用法使用有相应特征标的原型,C++将尝试使用标准类型转换强制进行匹配.
何时使用函数重载
虽然函数重载很吸引人,但也不要滥用.仅当函数基本上执行相同的任务,但使用不同形式的数据时, 才应采用函数重载.
使用一个带默认参数的函数要简单些.只需编写一个函数(而不是两个函数),程序也只需为一个函数 (而不是两个)请求内存;需要修改函数时,只需修改一个.然而,如果需要使用不同类型的参数,则默认参数便不管用了,在这种情况下,应该使用函数重载.
什么是名称修饰
C++如何跟踪每一个重载函数呢?它给这些函数指定了秘密身份。使用 C++开发工具中的编辑器编写 和编译程序时,C++编译器将执行一些神奇的操作—名称修饰(name decoration)或名称矫正(name mangling),它根据函数原型中指定的形参类型对每个函数名进行加密.请看下述未经修饰的函数原型: 这种格式对于人类来说很适合;我们知道函数接受两个参数(一个为int类型,另一个为float类型), 并返回一个long值.而编译器将名称转换为不太好看的内部表示,来描述该接口,如下所示:对原始名称进行的表面看来无意义的修饰(或矫正,因人而异)将对参数数目和类型进行编码.添加的一组符号随函数特征标而异,而修饰时使用的约定随编译器而异.
标准模板库
vector
举个例子:
1 |
|
size( )–返回容器的元素数目
swap( )–交换两个容器的内容
begin( )–返回一个指向容器中第一个元素的迭代器
end( )–返回一个超过容器尾的迭代器
什么是迭代器?TA是一个广义指针.事实上,TA可以是一个指针,也可以是一个类似指针的操作.迭代器让STL能够为各种不同的容器类(包括那些简单指针无法处理的类)提供统一的接口.每个容器类都定义了一个合适的迭代器,该迭代器的类型是一个名为iterator的typedef其作用域为整个类. 例如,要为vector的double类型规范声明一个迭代器,可以这样做:
1 | eg: |
回到前面的示例.什么是超过结尾(past-the-end)呢?它是一种迭代器,指向容器最后一个元素后面的那个元素.这与C-风格字符串最后一个字符后面的空字符类似,只是空字符是一个值,而”超过结尾” 是一个指向元素的指针(迭代器).end( )成员函数标识超过结尾的位置.如果将迭代器设置为容器的第一 个元素,并不断地递增,则最终它将到达容器结尾,从而遍历整个容器的内容.因此,如果 scores 和 pd 的定义与前面的示例中相同,则可以用下面的代码来显示容器的内容:
1 | for(pd=scores.begin();pd!=scores.end();pd++) |
所有容器都包含刚才讨论的那些方法.vector 模板类也包含一些只有某些 STL 容器才有的方法.push_back( )是一个方便的方法,它将元素添加到矢量末尾.这样做时,它将负责内存管理,增加矢量的长度,使之能够容纳新的成员.这意味着可以编写这样的代码:
1 | vector<double>scores; |
每次循环都给scores对象增加一个元素.在编写或运行程序时,无需了解元素的数目.只要能够取得足够的内存,程序就可以根据需要增加scores的长度.
erase( )方法删除矢量中给定区间的元素.它接受两个迭代器参数,这些参数定义了要删除的区间.了 解STL如何使用两个迭代器来定义区间至关重要.第一个迭代器指向区间的起始处,第二个迭代器位于区 间终止处的后一个位置.例如,下述代码删除第一个和第二个元素,即删除begin( )和begin( )+1指向的元素(由于vector提供了随机访问功能,因此vector类迭代器定义了诸如begin( )+2等操作):
1 | scores.erase(scores.begin(),scores.begin()+2); |


