类继承时函数掩盖盖问题
最后更新于:2022-04-01 14:24:13
正常情况下我们可以通过使用相同的函数名而不同的参数或者返回值类型等因素来实现函数重载。但是在类的继承时这样的情况无法实现,因为类继承时,同名函数将会被掩盖,只要同名不管函数参数和返回值等类型是否相同都会被掩盖掉。如下进行简单的测试就可以很清楚的了解这一特性了。
1.正常情况下的函数重载:
~~~
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
void bar(int c)
{
cout << "In function : void bar(int c) : c = " << c << endl;
}
void bar()
{
cout << "In function : void bar()" << endl;
}
int main()
{
int c = 2;
bar(c);
bar();
return 0;
}
~~~
以上代码就是一个最简单的重载代码。
2.在类的继承关系中的情况
~~~
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
namespace iaccepted
{
class A
{
public:
void bar(int c)
{
cout << "In function : void bar(int c) : c = " << c << endl;
}
};
class B : public A
{
public:
void bar()
{
cout << "In funcion : void bar()" << endl;
}
};
}
int main()
{
int c = 2;
iaccepted::B b;
b.bar();
b.bar(c);//错误,b中没有已int为参数的bar函数可供调用
return 0;
}
~~~
这就是在类的继承关系中类似的情形。A中有一个bar(int)类型的函数,B中有一个bar()类型的函数,其实类B虽然继承类A中的内容,但是由于B中的bar与A中的函数同名,不管参数或者返回值是否相同,B中的bar都会掩盖掉A中的所有名为bar的函数(如果不止存在一个的话)。
结论:
类的继承关系中派生类中的函数会掩盖基类中的同名函数,而不管这些函数的参数和返回值类型等其他因素。
动态生成对象初始化细节
最后更新于:2022-04-01 14:24:11
~~~
①T *p =new T;
②T *p =new T();
~~~
这两类用法不同点的总结。
1.若T为类类型,且用户定义了构造函数,则两种形式的效果完全相同,都会调用这个定义了的构造函数来初始化内部成员变量,但是如果此构造函数中并未对成员变量初始化,则这个时候内部的成员变量进行默认初始化——值是未定义的。
2.若T为类类型,但是用户并没有定义任何构造函数,则我们可以知道编译器会为该类合成一个默认的构造函数,这个时候上述两种形式的结果就不同了,①的类内部的成员变量这个时候执行默认初始化,其值是未定义的。但是在②中就不同了,加了括号后,p内部的成员变量会执行值初始化,即以0的形式进行初始化(整数就为0,bool就为false,string 就为空)
3.若T为内置类型,则①的形式中*p的值为未定义的,②中进行值初始化如上。
下面针对上面各种情况进行编程测试。
1.显示定义构造函数,但构造函数并不对成员变量进行初始化,则无论①②都输出的a都是未定义的值。
~~~
class Test{
public:
Test(){
printf("constructor\n");
}
int getA()const{
return a;
}
private:
int a;
};
int main(){
Test *pta = new Test;
Test *ptb = new Test();
printf("a : %d\n", pta->getA());
printf("a : %d\n", ptb->getA());
return 0;
}
~~~
2.显示定义构造函数,并且构造函数中对成员变量就行初始化,则①②中a都是构造后的值。
~~~
class Test{
public:
Test() : a(0){
printf("constructor\n");
}
int getA()const{
return a;
}
private:
int a;
};
int main(){
Test *pta = new Test;
Test *ptb = new Test();
printf("a : %d\n", pta->getA());//output zero
printf("a : %d\n", ptb->getA());//output zero
return 0;
}
~~~
以上两个例子说明,只要定义了构造函数,无论new的时候加不加括号,都会调用自身已有的构造函数。
3.类中不定义任何构造函数,而使用编译器合成的默认构造函数。
~~~
class Test{
public:
int getA()const{
return a;
}
private:
int a;
};
int main(){
Test *pta = new Test;
Test *ptb = new Test();
printf("a : %d\n", pta->getA());//undefined value a->default initializer
printf("a : %d\n", ptb->getA());//output zero a->value initializer
return 0;
}
~~~
4.内置类型的两种形式。
~~~
int main(){
int *pia = new int;
int *pib = new int();
printf("%d\n", *pia);//undefined value
printf("%d\n", *pib);//ouput zero
return 0;
}
~~~
有序容器自主定义排序器
最后更新于:2022-04-01 14:24:08
STL中的set和map是有序容器,使用时如果希望根据自己的需要来设定排序器,通常有一下两种方式。
1.如果容器中直接存储对象的话,那么我们可以在对象类中重载<即可,内置类型的话就不需要了,因为有默认的
2.如果存储的不是直接对象的话比如对象的指针(通常为智能指针),这个时候我们就要定义自己的比较器。而比较器的写法一般有两种。
->1.类内重载函数调用运算符的方法。
->2.以函数的方式提供比较器。
对于第一种方法是非常简单而且经常用的,这里不再赘述。
下面主要以一个简单的例子来介绍第二种方法中比较器的两种写法(这里为了方便说明,暂时存储对象)。
student.h
~~~
class Student{
public:
Student(const string &sid) : id(sid){}
void print()const{
cout << id << endl;
}
string getId()const{
return id;
}
private:
string id;
};
~~~
main.cpp
~~~
struct Compare{
//override the operator ()
bool operator()(const Student &ls, const Student &rs)const{
return ls.getId() < rs.getId();
}
};
bool compare(const Student &ls, const Student &rs){
return ls.getId() < rs.getId();
}
int main(){
/*the first type-----define a class as the comparator*/
set<Student, Compare> ms;
ms.insert(Student("222"));
ms.insert(Student("111"));
ms.insert(Student("333"));
auto ite = ms.begin();
for (; ite != ms.end(); ++ite){
ite->print();
}
/*the second type----define a function as the comparator*/
/*
set<Student, bool (*)(const Student &ls, const Student &rs)> ms(compare);
ms.insert(Student("222"));
ms.insert(Student("111"));
ms.insert(Student("333"));
auto ite = ms.begin();
for (; ite != ms.end(); ++ite){
ite->print();
}
*/
return 0;
}
~~~
在重载运算符时,类的对象可以直接访问私有成员解惑
最后更新于:2022-04-01 14:24:06
以前对这里确实有点疑惑,但是最近在看其他书的时候突然看到这么一句话:
实践证明,类(class)私有成员可以被类成员函数访问,不区分成员在哪个实例(instance)里。
也就是说,在类内部的成员函数中,哪怕是传入的对象,也是可以直接访问该对象的私有成员。(前提是该对象必须是本类型的一个对象)
这样类对象可以直接访问私有成员就合情合理了,而且这么做也确实是方便的。
C++11新标准之decltype的使用注意
最后更新于:2022-04-01 14:24:04
c++11新特性——decltype
decltype是C++11添加的一个新的关键字,目的是选择并返回操作数的数据类型,重要的是,在此过程中编译器分析表达式并得到它的类型,却不实际计算表达式的值。
对于内置类型的对象,使用decltype很直观,但当参数为复合类型的时候就应该注意一些使用细节问题。
1.当decltype作用于数组的时候就应该小心了,本文作者(CSDN iaccepted)。
~~~
intiarr[10] = {0};
decltype(iarr)ib;
~~~
这个时候ib的定义等价于 int ib[10];两者是一样的,不要认为ib是一个指针了,它是一个正宗的数组。我们可以验证一下:
~~~
cout<< sizeof(ib) << endl;
~~~
如果ib是10个元素数组的指针很明显将输出4,但是如果ib表示数组类型则会输出4*10 = 40.这地方完全类似于原有的typedef关键字。
~~~
typedefint iarr[10];
iarrib; //跟这里的decltype(iarr) ib是一样的功能。
~~~
2.就是因为上述的这个细节,在写函数返回值的时候就要注意类型问题。
~~~
decltype(iarr)function(){
//本文作者(CSDN iaccepted)
return***;
}
~~~
上述的语句就是错误的,因为很明显decltype(iarr) 表示以数组作为返回值,这在c++中是不允许的。
~~~
intia[3];
intiaa[][3] = { { 0 }, { 1 }, { 2 }, { 3 }, { 4 } };
intiab[][3] = { { 1 }, { 2 }, { 3 }, { 4 }, { 5 } };
~~~
这时候我们写个函数通过参数决定是使用iaa数组还是iab数组,也就是返回值要返回哪个数组首地址,要返回二维数组的首地址当然有多种写法,这里主要说一下使用decltype的注意点。
~~~
decltype(ia)*function(int index){
if (index == 1)returniaa;
elsereturn iab;
}//本文作者(CSDN iaccepted 凌风)
~~~
这样就能实现,decltype(ia)返回一个数组类型,该类型为指向一个有三个整形元素的数组,所以decltype(ia) * 就能表示一个指向数组元素的指针,即该指针指向一个数组,数组内的每个元素又是包含三个整数的数组元素。以上面的例子来说,函数的返回值若记为p,则p指向iaa[0]而p+1则指向iaa[1],*p 为iaa[0][0]的地址 *p + 1 为iaa[0][1]的地址,这里就说多了,因为这就是普通指针的特性,ok。
3.当decltype参数为指针的解引用的话就要注意了,此时返回引用类型而不是解引用后的类型。
~~~
intia[3] = { 1, 2, 3 };//本文作者(CSDN iaccepted 凌风)
~~~
decltype(*ia)b; //这就会出错,因为此时b是一个指向整形变量的引用,而引用必须在定义的时候初始化。
这里为什么返回引用其实很好理解,因为*ia就是当前指针所指对象的一个引用,因为我们可以直接给*ia赋值来改变ia所指对象的值,比如
~~~
*ia= 5;
~~~
此时ia[0] 就变成5. 同理
~~~
decltype(ia[1])b; //也是错误的,因为b为一个引用必须初始化。
//本文作者(CSDN iaccepted 凌风)
~~~
好了,暂时就想到这么多,先写到这吧
本文作者:CSDN iaccepted 凌风,博客地址:[http://blog.csdn.net/iaccepted](http://blog.csdn.net/iaccepted)
字面值初始化字符数组及字符串拷贝注意
最后更新于:2022-04-01 14:24:01
~~~
char carr[] = "author:CSDN-iaccepted";
~~~
此时,carr数组中元素的个数为21,最后一个位置用来存放空字符'\0'。
所以如果写成carr[21] = "author:CSDN-iaccepted";就是错误的。
~~~
int clen = strlen(carr);
int size = sizeof(carr);
~~~
这时候得到的clen = 21,size= 22。这就是因为strlen统计串中字符的个数,不计算最后的空字符,但是sizeof 是得到carr这整个字符串分配的内存大小,因为最后必须留一个位置来存放空字符,所以,占用22个内存单元,又因为1个字符类型占用1个字节,所以结果返回22(22个字节的内存空间)。
strcpy(char *dest,const char *src);这个函数在用的时候要注意,如下
~~~
char src[] = "helloworld";
char dest[] = "hello";
~~~
这个时候执行strcpy(dest,src);会导致程序溢出而崩溃,所以通常的方法就是使用strncpy来代替strcpy。
strncpy的最后一个参数为size_t 类型的计数count,指明要拷贝长度。这个长度指定为strlen(dest)很合适了,因为strlen返回值不计算最后一个空字符,这样,src字符串的长度如果超过dest,那么只会拷贝跟dest一样的长度,这样dest的末尾正好还是有一个空字符的,这样是不会出错的。但是有的时候我们定义的src不一定含有末尾的空字符,这个时候strlen(str)就会出错。如下
~~~
char src[] = {'h','e','l','l','o'};
char dest[] = "he";
~~~
这个时候就不能将拷贝长度指定为strlen(dest),所以一般情况下指定为strlen(dest)是非常危险的操作。这个时候要用到的就是sizeof了。
~~~
strncpy(dest,src,sizeof(dest));
dest[sizeof(dest)-1] = 0;
~~~
这样两条指令配合使用就不会发生错误了,所以,这里两行指令是拷贝字符串最安全的写法了(个人觉得)。
抽象的理解
最后更新于:2022-04-01 14:23:59
~~~
#include <iostream>
using namespace std;
class A{
public:
void a(){
cout << "func_a" << endl;
}
};
int main(){
A * p = NULL;
p -> a();
return 0;
}
~~~
该程序输出func_a,试分析其背后的原理
这主要涉及的是C++的内存模型问题,其实就是C++的抽象机制
C++对象虽然封装了成员函数、成员变量(属性),但成员函数和成员变量的处理方法是完全不同的,成员函数是整个类公有的,而成员数据才是一个对象真正私有的。只需要一个表示该类型的指针就可以直接访问public成员函数,不管该指针是否是野指针。而成员数据必须是一个有效的地址才可以访问。因为对每个对象建立一个一模一样的函数是完全没必要的,而每个对象的数据才会是不同的。
产生随机数
最后更新于:2022-04-01 14:23:57
c/c++中产生随机数使用rand()函数,但是这样每次启动程序时产生的随机数序列都是相同的,所以,这样产生的并不是真正的随机数列。
这里我们可以使用srand函数对随机数产生器进行初始化,而使用srand初始化的时候,需要给其提供一个种子,不同的种子就可以对应不同的随机数序列,但是如果种子相同,rand()产生的随机数序列还是相同的,所以,srand(1)进行初始化,显然不符合我们产生随机数的要求,通常的做法就是利用time函数来获得当前系统时间距离00:00:00GMT,January,1,1970这个时刻的秒数。然后强转成unsigned类型来作为种子,这样,能保证每次的种子都不相同。所以,产生随机数的函数如下:
~~~
srand((unsigned)time(NULL));
int a = rand();
~~~
这样就能够产生符合我们需要的随机数序列了。
ps:rand函数在stdlib包中;time函数在time包中;srand函数也在stdlib包中;莫忘导入包。
类中的几个特殊函数
最后更新于:2022-04-01 14:23:55
C++是面向对象的编程语言,编程中我们不可避免的需要自定义的类,在定义自己的类时我们应该关注类中的几个基本函数的定义——构造函数,复制构造函数,析构函数,赋值操作函数。现在简单介绍一下以上几个函数,由于水平有限,不足之处欢迎大家指正,以便互相学习,本人博客http://blog.csdn.net/IAccepted。
### 一.构造函数
我们在定义类时必须存在构造函数,但这里的必须存在并不是说程序员自己必须定义自己的构造函数,因为如果程序员不显示定义,编译器会生成一个默认构造函数。构造函数是一个特殊的成员函数,创建类类型的对象时都会调用构造函数。我们可以重载构造函数来实现不同的初始化规则。但是值得注意的是一旦显示定义了有参构造函数,系统就不会再生成默认无参构造函数。这通常会带来以下不便:
1.生成该类的每个对象都必须传递初始化参数进行初始化;
2.该类型不能用作动态分配数组的元素类型;
3.该类类型的静态分配数组必须为每个元素提供一个显示的初始化式;
实际上,如果定义了其他的构造函数,则提供一个默认构造函数几乎总是对的【C++ primer】http://blog.csdn.net/IAccepted
当然我们还可以编写构造函数来实现类类型对象的隐式转换,着相对较简单不再详述,欢迎访问本人博客:http://blog.csdn.net/IAccepted
### 二.复制构造函数
一般情况下利用一个同类对象初始化一个对象和简单的复制一个对象很简单。很多时候复制构造函数我们并不显示使用,在实参为类对象类型或返回值为类对象类型的值时都会调用复制构造函数,与构造函数类似,如果程序员不显示的定义复制构造函数,编译器也会为我们合成一个复制构造函数。一般情况下,类中如果只包含类类型成员或C++内置除指针外的类型的话,编译器为我们合成的默认复制构造函数就可以实现复制,无需显示定义复制构造函数,对于类中的类类型成员,会再调用自己的复制构造函数进行复制,类似与解箱操作,一层层的复制,知道最终全部为值类型。
注意指针类型是个特例,因为默认的复制构造函数只进行简单的值复制,所以如果类中存在指针,则经过复制,两个对象的指针成员就会指向同一个内存地址,这样是很危险的,首先两对象变的不再独立,任何一个对象修改了其中指针变量指向的内存中的值,另一个对象也会受到影响,另外,删除一个对象时,析构函数会释放其中指针类型所指向的内存空间,这样会造成另一个对象中的指针变成悬垂指针,有关指针的介绍大家可以查看本人的另一篇博客http://blog.csdn.net/iaccepted/article/details/6741600 。通常实现带指针类型对象的复制构造我都是使用类似值类型的方式来实现的,即为每个副本都重新new一个指针类型的对象然后进行值拷贝。当然也可以使用智能指针。
### 三.析构函数
析构函数就相对比较简单,主要针对的就是分配了系统资源的类对象在生命周期结束是回收资源,这里的生命周期可以是程序结束或者delete一个动态分配内存的对象。
如果类需要析构函数,则它也需要赋值操作符和复制构造函数。这是因为本类涉及到非值类型的成员,根据前面说的复制构造函数的内容,这点很容易理解,这是一个非常有用的规则,欢迎访问本人博客:http://blog.csdn.net/IAccepted
### 四.赋值操作符
可以重载赋值操作符=,赋值操作符不需要分配新对象,只是抱枕给其中的指针所指向的对象赋新值,而不是给指针本省复制。其返回值必须为*this的引用
即
~~~
classA classA::operator=(const classA& a){
……
……
return *this;
}
~~~
一般而言,赋值操作符与复合赋值操作符都应返回这样的做操作数引用。
欢迎访问本人博客:[http://blog.csdn.net/IAccepted](http://blog.csdn.net/IAccepted)
转载请注明出处!
标准IO库
最后更新于:2022-04-01 14:23:52
IO操作需要支持不同类型的设备和不同类型数据的接口。所以IO标准库使用了继承来定义一组面向对象的类。
### 1.首先IO类型在三个头文件中定义
iostream定义控制窗口读写的类型,fstream定义读写已命名文件的类型,sstring定义读写存储在内存中string对象(这个在做ACM的很多题目的时候很方便,这个在后面会提一下)。由于集成关系加上多态的特性(动态绑定)我们可以用istream&即使用istream的一个引用来作为形参,这样既可以使用istream对象做实参,也可以使用ifstream和istringstream对象来做实参,同理ostream&引用类型类似,这里不做复述了。
欢迎访问本人csdn博客:http://blog.csdn.net/IAccepted
### 2. IO对象不可以复制或赋值
原因:之所以不能被复制原因就是IO对象的复制构造函数是私有的。
导致的结果:
1).只有支持复制的元素类型可以存储在vector或其他容器里,由于流对象不能被复制的原因,所以流对象不能存储在容器中。
2).形参或返回类型也不能为流对象。
原因是流类的复制构造函数是私有的,不能被调用,而我们都知道当形参或返回值为对象类型时都要调用对象的复制构造函数进行对象的拷贝。
如果需要传递或返回IO对象,则必须传递或返回只想该对象的指针或引用。
欢迎访问本人csdn博客:http://blog.csdn.net/IAccepted
### 3.输出缓冲区管理
每个IO对象管理一个缓冲区,用于存储程序读写的数据。通常以下几种情况将导致缓冲区的内容被刷新(即写入到真实的输出设备或者文件中)。
1).程序正常结束。作为main返回工作的一部分,将清空所有输出缓冲区。
2).缓冲区已满,这时缓冲区将会在写入下一个值之前刷新。
3).程序员自己显示刷新,这就依靠特定的操作符,通常用endl。
4).将输出流与输入流关联,这样,在读入时将刷新关联的输出缓冲区。
### 4.文件的输入和输出
早期C语言的原因,IO标准库使用是C语言风格的字符串而不是C++风格的string字符串作为文件名,所以通常我们会讲string类型的字符串通过调用c_str类型转化为C风格字符串进行文件打开。
1).要养成打开文件后文件打开状态检测的好习惯。
2).当用一个流去操作多个文件时一定要清除文件流的状态即调用clear()方法。因为关闭流不能改变流对象的内部状态,如果一次读写操作失败,即使关闭流对象再打开也不能避免上次保持的错误状态,所以一定要调用clear()来清除这种错误状态。
3).文件模式只是文件的属性,而不是流的属性。ofstream::app ofstream::in ofstream::out 等等。
### 5.字符串流
在ACM中有些题目是整行读入一个句子然后逐个单词进行处理,这个时候字符串流就会使得操作相当的简单。
例如杭电OJ2072,代码如下,使用了istringstream后就很方便。
~~~
#include <iostream>
#include <set>
#include <sstream>
#include<string.h>
using namespace std;
int main()
{
string ss;
while(getline(cin,ss) && ss!="#")
{
istringstream stream(ss);
set<string> s; //放在这里,每次都是新容器
string word;
while(stream >>word)
{
s.insert(word);
}
cout<<s.size()<<endl;
}
return 0;
}
~~~
欢迎访问本人csdn博客:http://blog.csdn.net/IAccepted
static类成员使用注意
最后更新于:2022-04-01 14:23:50
对于某一特定类类型的全体对象我们需要访问一个共同的变量,这个时候我们通常是在类的声明中声明一个静态变量即static变量。当然可以用一个全局变量来代替,但是用全局变量是一个很不好的习惯,重要的一点就是全局变量会破坏类的封装。然而类中static变量不像普通变量,它独立与该类的任意对象而存在,每个static数据成员与一个特定的类相关连,而非与该类的对象相关连。[http://blog.csdn.net/IAccepted](http://blog.csdn.net/IAccepted)
### 一.使用类的static成员的优点:
1.static成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象的名字发生冲突。
2.可以实施封装。static成员可以是私有成员,而全局对象不可以。[http://blog.csdn.net/IAccepted](http://blog.csdn.net/IAccepted)
3.通过阅读程序容易看出static成员是与特定类关联的。这种可见性可以清晰地显示程序员的意图。
### 二.static类成员的生命与定义[http://blog.csdn.net/IAccepted](http://blog.csdn.net/IAccepted)
static类成员在声明类的时候同时被声明,static类成员的定义应该放在该类的实现文件中。即类声明在A.h中,而类的实现放在A.cpp中则,static类型变量的定义就放在A.cpp中。
格式为<数据类型> <类名>::<static变量名>=<初始值>
### 三.static修饰const类型的变量
当static修饰const类型的变量时,此变量可以在类的声明中直接初始化,但是该数据成员仍然必须在定义体之外进行定义。
宏展开出错
最后更新于:2022-04-01 14:23:48
当我们在分析有关宏定义的问题时,最简单的办法就是先把宏的内容进行替代,然后在进行分析。
如:
~~~
#define sqr(x) x+x
#define fun(x) x*x
~~~
则分析如下表达式的结果
~~~
sqr(3) //第一个很简单就是x+x即3+3=6
fun(3+3) //第二个先用宏替代则为 3+3*3+3则为15
!sqr(3) //第三个用宏代替为 !3+3=0+3=3 所以结果为3
~~~
strcpy
最后更新于:2022-04-01 14:23:46
### 题目:
已知strcpy函数的原型是:
~~~
char * strcpy(char * strDest,const char * strSrc);
~~~
1.不调用库函数,实现strcpy函数。
2.解释为什么要返回char *。
### 解说:
1.strcpy的实现代码
~~~
char * strcpy(char * strDest,const char * strSrc)
{
char * strDestCopy=strDest; //[3]
if ((strDest==NULL)||(strSrc==NULL)) //[1]
throw "Invalid argument(s)"; //[2]
while ((*strDest++=*strSrc++)!='\0'); //[4]
return strDestCopy;
}
~~~
错误的做法:
[1]
(A)不检查指针的有效性,说明答题者不注重代码的健壮性。
(B)检查指针的有效性时使用((!strDest)||(!strSrc))或(!(strDest&&strSrc)),说明答题者对C语言中类型的隐式转换没有深刻认识。在本例中char *转换为bool即是类型隐式转换,这种功能虽然灵活,但更多的是导致出错概率增大和维护成本升高。所以C++专门增加了bool、true、false三个关键字以提供更安全的条件表达式。
(C)检查指针的有效性时使用((strDest==0)||(strSrc==0)),说明答题者不知道使用常量的好处。直接使用字面常量(如本例中的0)会减少程序的可维护性。0虽然简单,但程序中可能出现很多处对指针的检查,万一出现笔误,[编译器](http://baike.baidu.com/view/487018.htm)不能发现,生成的程序内含逻辑错误,很难排除。而使用NULL代替0,如果出现拼写错误,编译器就会检查出来。
[2]
(A)return new string("Invalid argument(s)");,说明答题者根本不知道返回值的用途,并且他对内存泄漏也没有警惕心。从函数中返回函数体内分配的内存是十分危险的做法,他把释放内存的义务抛给不知情的调用者,绝大多数情况下,调用者不会释放内存,这导致内存泄漏。
(B)return 0;,说明答题者没有掌握异常机制。调用者有可能忘记检查返回值,调用者还可能无法检查返回值(见后面的链式表达式)。妄想让返回值肩负返回正确值和异常值的双重功能,其结果往往是两种功能都失效。应该以抛出异常来代替返回值,这样可以减轻调用者的负担、使错误不会被忽略、增强程序的可维护性。
[3]
(A)忘记保存原始的strDest值,说明答题者逻辑思维不严密。
[4]
(A)循环写成while (* strDest++=*strSrc++);,同[1](B)。
(B)循环写成while (* strSrc!='\0') *strDest++=*strSrc++;,说明答题者对边界条件的检查不力。循环体结束后,strDest字符串的末尾没有正确地加上'\0'。
2.返回strDest的原始值使函数能够支持链式表达式,增加了函数的“附加值”。同样功能的函数,如果能合理地提高的可用性,自然就更加理想。
万能const限定符与指针
最后更新于:2022-04-01 14:23:43
不记得谁说过,能用const的时候尽量用const。确实,const为增加程序的健壮性做出了很大贡献,但是const修饰指针的时候还是值得注意的。
### 一.指向const对象的指针;
如果指针指向const对象,那么很显然不能通过指针改变其所指向的const值。为了保证这个特性,C++语言强制要求指向const对象的指针也必须要具有const特性。
~~~
const double = 1.0;
const double *cpt;
cpt=&d;
~~~
const类型的指针变量为什么可以重新赋值?其实这里的cpt是一个指向double类型const对象的指针,const限定的是cpt指针所指向的对象类型,而并非cpt本身。也就是说,cpt本身并不是const。所以cpt在定义的时候不需要一定初始化,允许给cpt重新赋值,指向另一个const对象。
需要注意:可以把一个普通对象的地址赋给指向一const对象的指针,但不可以把一个const对象地址赋给一个普通类型指针,否则会出现编译错误。不能使用指向const对象的指针修改基础对象。无论const指针指向的对象是不是const类型,系统都会把它所指的所有对象都视为const,仅限于该指针的const对象(通过该指针不能修改其值);
### 二.const指针;
除指向const对象的指针外,C++语言还是提供了const指针——本身的值不能修改。
~~~
int iNum = 1;
int * const icpt=&iNum;
~~~
这里的icpt是const类型——不能再指向其他的对象。任何企图给const指针赋值的行为都回导致编译错误。这里值得注意的是指针本身的const类型并没有说明是否能用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
~~~
int iNum = 5;
int * const icpt=&iNum;
*icpt=4;//这里完全可以,因为iNum是普通变量
~~~
### 三.指向const对象const指针;
这里要说明的就是上两种情况的结合出现,即const类型的指针指向了const类型的对象。
~~~
const double pi=3.14;
const double * const pt=π
~~~
今天先总结这么多了,欢迎大家留言交流学习。
浅谈指针使用中注意事项
最后更新于:2022-04-01 14:23:41
相信大家对指针的用法已经很熟了,这里也不多说些定义性的东西了,只说一下指针使用中的注意事项吧。
一.在定义指针的时候注意连续声明多个指针时容易犯的错误,例如int * a,b;这种声明是声明了一个指向int类型变量的指针a和一个int型的变量b,这时候要清醒的记着,而不要混淆成是声明了两个int型指针。
二.要避免使用未初始化的指针。很多运行时错误都是由未初始化的指针导致的,而且这种错误又不能被编译器检查所以很难被发现。这时的解决办法就是尽量在使用指针的时候定义它,如果早定义的化一定要记得初始化,当然初始化时可以直接使用cstdlib中定义的NULL也可以直接赋值为0,这是很好的编程习惯。
三.指针赋值时一定要保证类型匹配,由于指针类型确定指针所指向对象的类型,因此初始化或赋值时必须保证类型匹配,这样才能在指针上执行相应的操作。
四.void * 类型的指针,其实这种形式只是记录了一个地址罢了,如上所说,由于不知道所指向的数据类型是什么所以不能进行相应的操作。其实void * 指针仅仅支持几种有限的操作:1.与另外的指针进行比较,因为void *类型里面就是存的一个地址,所以这点很好理解;2.向函数传递void *指针或从函数返回void *指针,举个例子吧,我们平时常用的库函数qsort中的比较函数cmp(个人习惯于用这个名字)中传递的两个参数就是const void *类型的,用过的应该很熟了;3.给另一个void * 类型的指针赋值。还是强调一下不能使用void * 指针操纵它所指向的对象。
五.不要将两个指针变量指向同一块动态内存。这个容易引起很严重的问题。如果将两个指针变量指向同一块动态内存,而其中一个生命期结束释放了该动态内存,这个时候就会出现问题,另一个指针所指向的地址虽然被释放了但该指针并不等于NULL,这就是所谓的悬垂指针错误,这种错误很难被察觉,而且非常严重,因为这时该指针的值是随机的,可能指向一个系统内存而导致程序崩溃。但也就是因为值是随机的,所以运行程序时有时正常有时崩溃,这一点要特别注意。
六.在动态delete释放一个指针所指向的内存后注意将该指针置空。
七.在为一个指针再次分配内存之前一定要保证它原先没有指向其他内存,防止出现内存泄漏。解决的办法是我们必须判断该指针是否为空,这时候就显示出第六条的优势,因为如果释放某内存后相应指针不置空的话就不能为其分配新内存了。所以第六条很有必要。
八.虽然程序在退出main函数时会释放所有内存空间,但对于大型程序最好还是某块内存不用了立刻释放,而不要指望系统最后的回收,因为内存泄漏会慢慢消耗系统资源直到内存不足而程序死掉。
九.在用new动态分配完内存之后一定要判断是否分配成功,分配成功后才能使用。
最后提醒两条:任何指针声明后一定要初始化;任何指针用free或delete释放之后一定要置空。
希望大家互相交流学习,本文博客地址: [**http://blog.csdn.net/iaccepted/article/details/6741600**](http://blog.csdn.net/iaccepted/article/details/6741600)
标准库string类型
最后更新于:2022-04-01 14:23:39
### 一. 首先作为一种标准库类型,string存在四种基本的构造函数。如下:
string s; //默认构造函数,s为空串
string s(s1);//用s1来初始化s
string s("My Blog [http://blog.csdn.net/IAccepted](http://blog.csdn.net/IAccepted)"); //将s初始化为一个字符串字面值
string s(n,'c'); //将s初始化为n个‘c’的副本
### 二.对于输入主要就是cin>>s;
(1)读取并忽略开头所有的空白字符。
(2)读取字符直至再次遇到空白字符,读取终止。
(3)输入操作符会返回所读的数据流。
当要读入一整行时可以使用getline函数,getline函数需要两个参数第一个为输入流 对象,第二个为一个string类型对象。值得注意的是getline并不忽略行开头的空白字 符。
### 三.string::size_type类型
(1)size()函数我们平时都直接默认它返回int类型的值,事实上size()函数返回的是string::size_type类型的值。
(2)string类类型和许多其他类型都定义了一些配套类型。重要的是通过这种配套类型,库类型的使用就能与机器无关。size_type就是这些配套类型中的一种。
(3)建议string的size操作结果的变量为string::size_type类型。特别重要的是,尽量 不要把size的返回值赋值给一个int变量。
### 四.string对象的相加注意
当进行string对象和字符串字面值混合连接操作时,+操作符的左右操作数必
须至少有一个是string类型的对象。
例.s2=“world”; string s=“hellow”+“,”+s2;这里是错误的,因为第一个+
号的两边都为string字面值。
### 五.string转为char *
很多时候我们还是需要将string类型的转化为char*来实现自定义的操作,
C++标准库也为了和之前用C写的程序兼容,于是可以用string的c_str()函数。
string a="My Blog [http://blog.csdn.net/IAccepted](http://blog.csdn.net/IAccepted)";
char * b=a.c_str();//这样不能通过编译,原因是为了防止string对象内容被恶意修改,返回的是const类型的对象,所以要加const修饰
const char * b=a.c_str();//这样就能编译通过了。
简单总结了一点string使用时的注意事项,欢迎大家补充交流!
写头文件注意
最后更新于:2022-04-01 14:23:37
在C++中我们写头文件时经常需要#include来包含其他头文件。头文件定义的实体经常使用其他头文件的设施。
包含其他头文件是如此的司空见惯,甚至一个头文件被多次包含进同一源文件中也不是什么稀奇的事。例如一个头文件中用到string类型的变量,而包含这个头文件的源文件中也用到string类型的变量,这个时候string头文件就被包含了两次:一次是通过程序本身直接包含,另一次是通过包含自写头文件而间接包含。
因此,设计头文件的时候,应该使其可以多次包含在同一源文件中,这一点是很重要的。我们必须保证多次包含同一头文件不会引起该头文件的类和对象被多次定义。使得头文件安全的通用做法是使用预处理器定义的头文件保护符。头文件保护符用于避免在已经见到头文件的情况下重新处理该头文件的内容。
在编写头文件之前,我们需要引入一些额外的预处理器设施。预处理器允许我们自定义变量。
为了避免命名冲突,预处理器变量经常用全大写字母表示。预处理器变量有两种状态:一定义和未定义。定义预处理器变量和检测器状态所用的预处理器指示不同。#define指示接受一个名字并定义该名字为预处理器变量。#ifndef指示检测指定的预处理器变量是否未定义。如果预处理器变量未定义,那么跟在其后面的所有指示都被处理,直到出现#endif。为了保证头文件在给定的源文件中只处理一次,我们首先检测#ifndef。第一次处理头文件时,测试会成功,因为相应的预处理器还未定义。下一条语句就定义了该预处理器变量。那样的话,如果我们编译的文件敲好又一次包含了该头文件,#ifndef指示会发现该预处理器已经定义,并且忽略该头文件的剩余部分。
当没有两个头文件定义和使用同名的预处理器变量时,这个策略相当有效。我们可以用定义在头文件里的实体(如类)来命名预处理器变量来避免预处理器变量重名的问题。
注意:头文件应该包含有保护符,即使这些头文件不会被其他头文件包含。编写头文件保护符并不难,而且如果头文件被包含多次,它可以避免难以理解的编译错误。
使用内置算术类型
最后更新于:2022-04-01 14:23:34
C++中整型数有点令人迷惑不解。就像C语言一样,C++被设计成允许程序在必要的时候直接处理硬件,因此整型被定义成满足各种各样硬件的特性。大多数程序员可以(应该)通过限制实际使用的类型来忽略这些复杂性。
实际上,许多人用整型进行计数。例如:程序经常计算像vector或数组这种数据结构的元素的个数。其实标准库定义了一组类型用于统计对象的大小。因此,当计数这些元素时使用标准库定义的类型总是正确的。其他情况下,使用unsigned类型比较明智,可以避免值越界导致结果为负数的可能性。
当执行整型算术运算时,很少使用short类型。大多数程序中,使用short类型可能会隐含赋值越界的错误。这个错误会产生什么后果将取决所使用的机器。比较典型的情况是值“截断”以至于因越界而变成很大的负数。同样的道理,虽然char类型是整型,但是char类型通常用来存储字符而不用于计算。事实上,在某些实现中char类型被当作signed类型,在另外一些实现中则被当作unsigned类型,因此把char类型作为计算类型使用时容易出问题。
在大多数计算技上,使用int类型进行整型计算不易出错。就技术上而言,int类型用16位表示——这对大多数应用来说太小了。实际应用中,大多数通用机器都是使用和long类型一样长的32位来表示int类型。整型运算时,用32位表示int类型和用64位表示long类型的机器会出现应该选择int类型还是long类型的难题题(当然这对ACMer来说并不是什么难题可以瞬间判断)。在这些机器上,用long类型进行计算所付出的运行时代价远远高于int类型进行同样运算的代价,所以选择类型前要先了解程序的细节并且比较long类型与int类型的实际运行时性能代价。
决定使用哪种浮点型就容易多了:使用double类型基本上不会错。在float类型中隐式的精度损失是不能忽视的,而双精度计算的代价相对于单精度可以忽略。事实上,有些机器上,double类型比float类型的计算要快得多。long double类型提供的精度通常没有必要,而且还要承担额外的运行代价。
你能直接写出多少个C++关键字?
最后更新于:2022-04-01 14:23:32
一次性写出C++中的所有关键字,有人会问了,到底有几个呢? 下面来数数吧!
~~~
asm do if return try
auto double inline short typedef
bool dynamic_cast int signed typeid
break else long static union
case enum mutable static_cast unsigned
catch explicit namespace struct using
char export new switch virtual
class extern register sizeof typename
const false operator template void
const_cast float private this volatile
continue for protected throw wchar_t
default friend public true while
delete goto reinterpret_cast
~~~
前言
最后更新于:2022-04-01 14:23:30
> 原文出处:[关注C++细节](http://blog.csdn.net/column/details/iaccepted.html)
作者:[iaccepted](http://blog.csdn.net/iaccepted)
**本系列文章经作者授权在看云整理发布,未经作者允许,请勿转载!**
# 关注C++细节
> 这里介绍C++中经常被忽视的细节,细节决定成败,在C++的学习路上注重细节,真正学好C++。