目录
初始化列表及统一初始化方法 Initializer lists
本部分转载自https://www.cnblogs.com/mrlsx/p/5419030.html
野指针及c++指针使用注意点
避免野指针的产生
“野指针”的成因主要有:
1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
char *p; //此时p为野指针
2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.
char *p=new char[10]; //指向堆中分配的内存首地址,p存储在栈区
cin>> p;
delete []p; //p重新变为野指针
3)指针操作超越了变量的作用范围。
char *p=new char[10]; //指向堆中分配的内存首地址
cin>> p;
cout<<*(p+10); //可能输出未知数据
指针的注意点:
a.指针指向常量存储区对象
char *p="abc";
此时p指向的是一个字符串常量,不能对*p的内容进行写操作,如srtcpy(p,s)是错误的,因为p的内容为“abc”字符串常量,该数据存储在常量存储区,但可以对指针p进行操作,让其指向其他的内存空间。
b.资源泄漏
问题:
1 #include<iostream>
2 using namespace std;
3 void main()
4 {
5 char *p=new char[3]; //分配三个字符空间,p指向该内存空间
6 p="ab"; //此时p指向常量“ab”,而不再是new char分配的内存空间了,从而造成了资源泄漏
7 delete []p; //释放时报错
8 }
结果:卡死
改进:
1 #include<iostream>
2 using namespace std;
3 void main()
4 {
5 char *p=new char[3]; //分配三个字符空间,p指向该内存空间
6 strcpy(p,"ab"); //将"ab"存储到p指向的内存空间
7 delete []p; //ok
8 }
结果:正确
c.内存越界
1 char *p=new char[3]; //分配三个字符空间,p指向该内存空间
2 strcpy(p,"abcd"); //将abcd存处在分配的内存空间中,由于strlen("abcd")=4>3,越界
3 delete []p; //ok
d.返回值是指针
问题:数组p[]中的内容为“hello world”,存储在栈区,函数结束时内容被清除,p变为野指针,可能导致乱码
1 #include<iostream>
2 using namespace std;
3 char *f()
4 {
5 char p[]="abc";
6 return p;
7 }
8 void main()
9 {
10 cout<<f()<<endl;
11 }
结果:
改进:
1.加static限定,延长数组生存期
1 #include<iostream>
2 using namespace std;
3 char *f()
4 {
5 static char p[]="abc"; //此时数组为静态数组,存储在全局/静态区,生存期到程序结束,因此函数结束时不会销毁p
6 return p;
7 }
8 void main()
9 {
10 cout<<f()<<endl;
11 }
结果:
2.定义成指针型数组
1 #include<iostream>
2 using namespace std;
3 char *f()
4 {
5 char *p="abc"; //"abc"存储在文字常量区,p是指向常量的指针,生存期到程序结束
6 return p;
7 }
8 void main()
9 {
10 cout<<f()<<endl;
11 }
结果:
3.动态分配存储空间,存储在堆区
1 #include<iostream>
2 using namespace std;
3 char *f()
4 {
5 char *p=new char[5]; //动态分配存储空间,p指向堆区
6 strcpy(p,"abc"); // 这里不能用p="abc",前面已经说明
7 return p;
8 }
9 void main()
10 {
11 cout<<f()<<endl;
12 }
结果:
e.指针做形参
即所谓的地址传递,我们都知道地址传递的方式,形参的改变会导致实参的改变,但要注意的是,这里的改变是指指针所指内容的改变,而不是指针值的改变。因此,当形参改变会导致实参改变时,指针所指的内容是非const类型的,否则会出错。
1.改变指针内容:
1 void swap(int *a,int *b) //交换的是*a,*b,即指针的内容,而不是指针a,b
2 {
3 int t;
4 t=*a;
5 *a=*b;
6 *b=t;
7 }
2.改变指针值:
1 #include<iostream>
2 using namespace std;
3 void fun(char *p)
4 {
5 p="cba"; //“cba”存放在文字常量区,让p指向常量"abc",这里改变的是指针值,实参并不会改变
6 }
7 void main()
8 {
9 char *p="abc"; //“abc”存放在文字常量区,p指向常量"abc"
10 fun(p);
11 cout<<p<<endl; //输出"abc",而不是"bca"
12 }
结果:
继续看下面的情况,修改指针的内容:
1 #include<iostream>
2 using namespace std;
3 void fun(char *p)
4 {
5 p[0]='c'; //改变p的内容,即修改p[0]
6 }
7 void main()
8 {
9 char *p="abc"; //p指向常量"abc"
10 fun(p);
11 cout<<p<<endl; //error,p所指内容为常量,不能修改
12 }
结果:
注:p="ab"和strcpy(p,"ab"),含义不一样,前者指针p指向常量“ab”存储区域的首地址,改变了p最开始指向的new申请的内存空间;而后者是将“ab”分配到new申请的内存空间中;
本部分转载自https://www.cnblogs.com/codingmengmeng/p/5906282.html
C++ 类的静态成员及静态成员函数
- 对象与对象之间的成员变量是相互独立的。要想共用数据,则需要使用静态成员和静态方法。
- 只要在类中声明静态成员变量,即使不定义对象,也可以为静态成员变量分配空间,进而可以使用静态成员变量。(因为静态成员变量在对象创建之前就已经被分配了内存空间)
- 静态成员变量虽然在类中,但它并不是随对象的建立而分配空间的,也不是随对象的撤销而释放(一般的成员在对象建立时会分配空间,在对象撤销时会释放)。静态成员变量是在程序编译时分配空间,而在程序结束时释放空间。
- 静态成员的定义和声明要加个关键static。静态成员可以通过双冒号来使用,即<类名>::<静态成员名>。
- 初始化静态成员变量要在类的外面进行。初始化的格式如下:数据类型 类名::静态成员变量名 = 初值;
- 不能用参数初始化表,对静态成员变量进行初始化。
- 既可以通过类名来对静态成员变量进行引用,也可以通过对象名来对静态成员变量进行引用。
- 普通成员函数和静态成员函数的区别是:普通成员函数在参数传递时编译器会隐藏地传递一个this指针.通过this指针来确定调用类产生的哪个对象;但是静态成员函数没有this指针,不知道应该访问哪个对象中的数据,所以在程序中不可以用静态成员函数访问类中的普通变量.
下面通过几个例子来总结静态成员变量和静态成员函数的使用规则。
一、通过类名调用静态成员函数和非静态成员函数
1 //例子一:通过类名调用静态成员函数和非静态成员函数
2 class Point{
3 public:
4 void init()
5 {}
6
7 static void output()
8 {}
9 };
10
11 void main()
12 {
13 Point::init();
14 Point::output();
15 }
编译出错:错误 1 error C2352: “Point::init”: 非静态成员函数的非法调用
结论一:不能通过类名来调用类的非静态成员函数
二、通过类的对象调用静态成员函数和非静态成员函数
1 //例子二:通过类的对象调用静态成员函数和非静态成员函数
2 class Point{
3 public:
4 void init()
5 {
6 }
7
8 static void output()
9 {}
10 };
11
12 void main()
13 {
14 Point pt;
15 pt.init();
16 pt.output();
17 }
编译通过。
结论二:类的对象可以使用静态成员函数和非静态成员函数。
三、在类的静态成员函数中使用类的非静态成员
1 //例子三:在类的静态成员函数中使用类的非静态成员
2 #include <iostream>
3 using namespace std;
4
5 class Point{
6 public:
7 void init()
8 {
9 }
10 static void output()
11 {
12 cout << "m_x=" << m_x << endl;
13 }
14 private:
15 int m_x;
16 };
17 void main()
18 {
19 Point pt;
20 pt.output();
21 }
编译出错:IntelliSense: 非静态成员引用必须与特定对象相对
因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间,所以这个调用就会出错,就好比没有声明一个变量却提前使用它一样。
结论三:静态成员函数中不能引用非静态成员。
四、在类的非静态成员函数中使用类的静态成员
1 //例子四:在类的非静态成员函数中使用类的静态成员
2 #include <iostream>
3 using namespace std;
4
5 class Point{
6 public:
7 void init()
8 {
9 output();
10 }
11 static void output()
12 {
13 }
14 private:
15 int m_x;
16 };
17 void main()
18 {
19 Point pt;
20 pt.init();
21 }
编译通过。
结论四:类的非静态成员可以调用静态成员函数,但反之不能。
五、使用类的静态成员变量
1 //例子五:使用类的静态成员变量
2 #include <iostream>
3 using namespace std;
4
5 class Point{
6 public:
7 Point()
8 {
9 m_nPointCount++;
10 }
11 ~Point()
12 {
13 m_nPointCount++;
14 }
15 static void output()
16 {
17 cout << "m_nPointCount=" << m_nPointCount << endl;
18 }
19 private:
20 static int m_nPointCount;
21 };
22
23 void main()
24 {
25 Point pt;
26 pt.output();
27 }
链接出错:error LNK2001: 无法解析的外部符号 "private: static int Point::m_nPointCount" (?m_nPointCount@Point@@0HA)
这是因为类的成员变量在使用前必须先初始化。
改成如下代码即可:
1 #include <iostream>
2 using namespace std;
3
4 class Point{
5 public:
6 Point()
7 {
8 m_nPointCount++;
9 }
10 ~Point()
11 {
12 m_nPointCount++;
13 }
14 static void output()
15 {
16 cout << "m_nPointCount=" << m_nPointCount << endl;
17 }
18 private:
19 static int m_nPointCount;
20 };
21
22 //类外初始化静态成员变量时,不用带static关键字
23 int Point::m_nPointCount = 0;
24 void main()
25 {
26 Point pt;
27 pt.output();
28 }
运行结果:
结论五:类的静态成员变量必须先初始化再使用。
https://www.cnblogs.com/lidabo/p/7241389.html和https://www.cnblogs.com/me115/p/4800777.html
c++11的新特性
什么是C++0x?
C++0x是C++最新标准标准化过程中的曾用名,在这一系列文章中我们将介绍最新标准添加的一系列新的语言特性。在2011年9月份,C++0x正式由官方发布并命名C++11,现在很多编译器已经支持了部分C++11特性。
C++11包括大量的新特性:主要特征像lambda表达式和移动语义,实用的类型推导关键字auto,更简单的容器遍历方法,和大量使模板更容易使用的改进。这一系列教程将包含所以以上特性。
你该关注C++11吗?
很明显,C++11为C++带来了大量的新特性。C++11将修复大量缺陷和降低代码拖沓,比如lambda表达式的支持将使代码更简洁。像移动语义这种特性会提高语言内核的基础效率,使你可以写出更快的代码。对模板系统的优化可以使你更容易写出泛型的代码。
新的标准库同时也会包含新的特性,包括对多线程的支持和优化智能指针,后者将给那些还没用类似于boost::shared_ptr的人提供更简单的内存管理方法。
我已经开始使用新的C++11特性,并且非常喜欢:新的auto关键字,对模板”>>“写法的支持,lambda表达式和新的函数定义语法。
C++11是如何开发出来的?
C++11的出现,首先要感谢C++标准委员会的辛勤工作,一群来自学术界和工业界的专家,他们的多次会晤攻克难题,终于设计出了一种跨平台,被多种编译器支持,可以生成高效易维护代码的语言。而C++11新标准,就像对灵活强大的C++的一次不可思议的扩展。
C++11包括什么?
更易用的语言
使用过C++11后,我发现它提供了大量的基础方法使C++变成了一种更易使用的语言。这不是说它变成了简单的语言—这里有大量的新特性—提供了大量的方法使编程更容易。让我们看一个例子,auto关键字。在C++11中,假如编译器可以从变量的初始化中得到它的类型,那么你不必要指定类型。比如,你可以这样写:
|
编译器可以推导出y的类型是int。当然,这不是一个证明auto有用的一个闪亮的例子。当使用模板特别是STL时auto很好用。为什么这么说,想象使用一个迭代器(iterator):
|
现在你想遍历address_book中的元素,要这样做,你需要一个迭代器:
|
这是一个恐怖的长类型声明,当你已经知道这个类型的时候。这样是不是简洁多了:
|
代码变得更简单明了,我觉得可读性也更高了,因为模板语法使这一行其它内容变模糊了。这是我特别喜欢的一个特性,我发现它消除了许多头疼和难以追踪的编译错误,节省了时间而没有丢掉表达式的意思。
区间迭代(range-based for loop)
下面迭代器的例子是C++11提供的一种处理迭代的更好方法,有些人叫做区间迭代(基本上所有的现代语言都支持)。这个例子足够证明这种语法是多么优雅:
|
你需要做的就是给出一个变量和要迭代的区间。但是如果你想迭代一个map怎么办?你怎么设置map中值的类型?你知道vector值的类型是int。但map的值类型是pair,通过.first和.second给你提供键和值。但是用auto,你根本无需关心确切类型,你可以简单的写成:
|
这将打印出:
|
这是一个不错的C++11新特性组合用法吧?
>>(right angle brackets)
我这里还有更易用的优化—在以前的C++标准中,假如你写一个含有其他模板类型的模板:
|
你必须在结束的两个’>‘之间添加空格。这不仅烦人,而且当你写成>>而没有空格时,你将得到困惑和误导的编译错误信息。产生这种行为的原因是C++词法分析的最大匹配原则(maximal munch rule)。一个好消息是从今往后,你再也不用担心了:
|
对,这确实是个小东西,不过却是人工代码克服机器工具的胜利。另外,这样写就不那么丑了。
多线程
这是第一次,C++11将包含一种内存模型和对应的多线程库,这意味着你将可以编写符合标准的多线程代码。新标准将提供所有的通用线程方法,比如线程、线程局部存储和原子操作。它也提供了一系列有趣的特性:futures和promises。futures和promises的主要思想是,你可以编写代码表示,“这个对象,一个future代码还没计算完的结果”,它将可以在后台计算结果。当这个值需要的时候,你向future发请求,假如这个值准备好了,那就可以得到它,要不然继续等待。
我将在后续的文章中深入探讨多线程。
萃取类型 decltype
decltype实际上有点像auto的反函数,使用auto可以用来声明一个指定类型的变量,而decltype可以通过一个变量(或表达式)得到类型;
#include <vector>
int main() {
int x = 5;
decltype(x) y = x; //等于 auto y = x;
const std::vector<int> v(1);
auto a = v[0]; // a has type int
decltype(v[1]) b = 1; // b has type const int&, the return type of
// std::vector<int>::operator[](size_type) const
auto c = 0; // c has type int
auto d = c; // d has type int
decltype(c) e; // e has type int, the type of the entity named by c
decltype((c)) f = c; // f has type int&, because (c) is an lvalue
decltype(0) g; // g has type int, because 0 is an rvalue
}
有没有联想到STL中的萃取器?写模版时有了这个是不是会方便很多;
返回类型后置语法 Trailing return type
C++11支持返回值后置
例如:
int adding_func(int lhs, int rhs);
可以写为:
auto adding_func(int lhs, int rhs) -> int
auto用于占位符,真正的返回值在后面定义;
这样的语法用于在编译时返回类型还不确定的场合;
比如有模版的场合中,两个类型相加的最终类型只有运行时才能确定:
template<class Lhs, class Rhs>
auto adding_func(const Lhs &lhs, const Rhs &rhs) -> decltype(lhs+rhs)
{return lhs + rhs;}
cout << adding_func<double,int>(dv,iv) << endl;
auto用于占位符,真正的返回值类型在程序运行中,函数返回时才确定;
不用auto占位符,直接使用decltype推导类型:
decltype(lhs+rhs) adding_func(const Lhs &lhs, const Rhs &rhs)
这样写,编译器无法通过,因为模版参数lhs和rhs在编译期间还未声明;
当然,这样写可以编译通过:
decltype( (*(Lhs*)0) + (*(Rhs*)0) ) adding_func(const Lhs &lhs, const Rhs &rhs)
但这种形式实在是不直观,不如auto占位符方式直观易懂;
空指针标识 nullptr
空指针标识(nullptr)(其本质是一个内置的常量)是一个表示空指针的标识,它不是一个整数。这里应该与我们常用的NULL宏相区别,虽然它们都是用来表示空置针,但NULL只是一个定义为常整数0的宏,而nullptr是C++11的一个关键字,一个内建的标识符。
nullptr和任何指针类型以及类成员指针类型的空值之间可以发生隐式类型转换,同样也可以隐式转换为bool型(取值为false)。但是不存在到整形的隐式类型转换。
有了nullptr,可以解决原来C++中NULL的二义性问题;
voidF(int a){
cout<<a<<endl;
}
voidF(int*p){
assert(p != NULL);
cout<< p <<endl;
}
int main(){
int*p = nullptr;
int*q = NULL;
bool equal = ( p == q ); // equal的值为true,说明p和q都是空指针
int a = nullptr; // 编译失败,nullptr不能转型为int
F(0); // 在C++98中编译失败,有二义性;在C++11中调用F(int)
F(nullptr);
return 0;
}
lambda表达式的引入
对于为标准库算法写函数/函数对象(function object)这个事儿大家已经抱怨很久了(例如Cmp)。特别是在C++98标准中,这会令人更加痛苦,因为无法定义一个局部的函数对象。
首先,我们需要在我们实现的逻辑作用域(一般是函数或类)外部定义比较用的函数或函数对象,然后,才能使用:
bool myfunction (int i,int j) { return (i<j); }
struct myclass {
bool operator() (int i,int j) { return (i<j);}
} myobject;
int main()
{
int myints[] = {32,71,12,45,26,80,53,33};
std::vector<int> myvector (myints, myints+8);
// using function as comp
std::sort (myvector.begin(), myvector.end(), myfunction);
// using function object as comp
std::sort (myvector.begin(), myvector.end(), myobject);
}
不过现在好多了,lambda表达式允许用”inline”的方式来写函数了:
sort(myvector.begin(), myvector.end(), [](int i, int j) { return i< j; });
真是亲切!lambda的引入应该会增加大家对STL算法的使用频率;
原生字符串 Raw string literals
比如,你用标准regex库来写一个正则表达式,但正则表达式中的反斜杠’\’其实却是一个“转义(escape)”操作符(用于特殊字符),这相当令人讨厌。考虑如何去写“由反斜杠隔开的两个词语”这样一个模式(\w\\w):
string s = "\\w\\\\\\w"; // 不直观、且容易出错
请注意,在正则表达式和普通C++字符串中,各自都需要使用连续两个反斜杠来表示反斜杠本身。然而,假如使用C++11的原生字符串,反斜杠本身仅需一个反斜杠就可以表示。因而,上述的例子简化为:
string s = R"(\w\\\w)"; // ok
非成员begin()和end()
非成员begin()和end()函数。他们是新加入标准库的,除了能提高了代码一致性,还有助于更多地使用泛型编程。它们和所有的STL容器兼容。更重要的是,他们是可重载的。所以它们可以被扩展到支持任何类型。对C类型数组的重载已经包含在标准库中了。
在这个例子中我打印了一个数组然后查找它的第一个偶数元素。如果std::vector被替换成C类型数组。代码可能看起来是这样的:
int arr[] = {1,2,3};
std::for_each(&arr[0], &arr[0]+sizeof(arr)/sizeof(arr[0]), [](int n) {std::cout << n << std::endl;});
auto is_odd = [](int n) {return n%2==1;};
auto begin = &arr[0];
auto end = &arr[0]+sizeof(arr)/sizeof(arr[0]);
auto pos = std::find_if(begin, end, is_odd);
if(pos != end)
std::cout << *pos << std::endl;
如果使用非成员的begin()和end()来实现,就会是以下这样的:
int arr[] = {1,2,3};
std::for_each(std::begin(arr), std::end(arr), [](int n) {std::cout << n << std::endl;});
auto is_odd = [](int n) {return n%2==1;};
auto pos = std::find_if(std::begin(arr), std::end(arr), is_odd);
if(pos != std::end(arr))
std::cout << *pos << std::endl;
这基本上和使用std::vecto的代码是完全一样的。这就意味着我们可以写一个泛型函数处理所有支持begin()和end()的类型。
初始化列表及统一初始化方法 Initializer lists
在C++98中,对vector的多个初始化,我们需要这样:
int myints[] = { 10, 20, 30, 30, 20, 10, 10, 20 };
std::vector<int> myvector (myints, myints+8);
现在,我们可以这样:
std::vector<int> second ={10, 20, 30, 30, 20, 10, 10, 20};
初始化表有时可以像参数那样方便的使用。看下边这个例子(x,y,z是string变量,Nocase是一个大小写不敏感的比较函数):
auto x = max({x,y,z},Nocase());
初始化列表不再仅限于数组。对于常见的map、string等,我们可以使用以下语法来进行初始化:
int arr[3]{1, 2, 3};
vector<int> iv{1, 2, 3};
map<int, string> m{{1, "a"}, {2, "b"}};
string str{"Hello World"};
可以接受一个“{}列表”对变量进行初始化的机制实际上是通过一个可以接受参数类型为std::initializer_list的函数(通常为构造函数)来实现的。例如:
void f(initializer_list<int>);
f({1,2});
f({23,345,4567,56789});
f({}); // 以空列表为参数调用f()
f{1,2}; // 错误:缺少函数调用符号( )
years.insert({{"Bjarne","Stroustrup"},{1950, 1975, 1985}});
初始化列表可以是任意长度,但必须是同质的(所有的元素必须属于某一模板类型T, 或可转化至T类型的)。
容器可以用如下方式来实现“初始化列表构造函数”:
template<class E> class vector {
public:
// 初始化列表构造函数
vector (std::initializer_list<E> s)
{
// 预留出合适的容量
reserve(s.size()); //
// 初始化所有元素
uninitialized_copy(s.begin(), s.end(), elem);
sz = s.size(); // 设置容器的size
}
// ... 其他部分保持不变 ...
};
使用“{}初始化”时,直接构造与拷贝构造之间仍有细微差异,但不再像以前那样明显。例如,std::vector拥有一个参数类型为int的显式构造函数及一个带有初始化列表的构造函数:
vector<double> v1(7); // OK: v1有7个元素<br />
v1 = 9; // Err: 无法将int转换为vector
vector<double> v2 = 9; // Err: 无法将int转换为vector
void f(const vector<double>&);
f(9); // Err: 无法将int转换为vector
vector<double> v1{7}; // OK: v1有一个元素,其值为7.0
v1 = {9}; // OK: v1有一个元素,其值为9.0
vector<double> v2 = {9}; // OK: v2有一个元素,其值为9.0
f({9}); // OK: f函数将以列表{9}为参数被调用
vector<vector<double>> vs = {
vector<double>(10), // OK, 显式构造(10个元素,都是默认值0.0)
vector<double>{10}, // OK:显式构造(1个元素,值为10.0)
10 // Err :vector的构造函数是显式的
};
函数可以将initializer_list作为一个不可变的序列进行读取。例如:
void f(initializer_list<int> args)
{
for (auto p=args.begin(); p!=args.end(); ++p)
cout << *p << "\n";
}
仅具有一个std::initializer_list的单参数构造函数被称为初始化列表构造函数。
标准库容器,string类型及正则表达式均具有初始化列表构造函数,以及(初始化列表)赋值函数等。初始化列表亦可作为一种“序列”以供“序列化for语句”使用。
还有好多其他的东西
C++11特性的数量是客观的。你可以阅读维基百科里的C++11页面,而我计划在这一系列文章中深入探索这些特性,包括:
- 如何用auto、decltype和新的函数语法编写更好的代码
- Lambda表达式
- 区间迭代
- 常量表达式
- 右值引用和移动语义
- nullptr和强类型的枚举
类的深拷贝问题
创建一个类student,包含成员id和name,其中name是char*类型的变量
一旦出现char*类型的变量就要小心了浅拷贝的问题,即当另一个类对象使用拷贝构造或者赋值时,应该在内存中新申请一个内存空间存放他自己的char*name变量,而不能直接把他的name指向原对象的空间。
class Student
{
public:
Student()
{
this->id = 0;
this->name = NULL;
}
Student(int id, char *name)
{
this->id = id;
//this->name = name;
int len = strlen(name);
this->name = new char[len + 1];
strcpy(this->name, name);
}
Student(const Student &another)
{
this->id = another.id;
//深拷贝
int len = strlen(another.name);
this->name = new char[len + 1];
strcpy(this->name, another.name);
}
Student & operator=(const Student &another) //只要类中有char *name型的变量,一定要写等号重载和深拷贝,防止别人使用的时候出错
{
//1 防止自身赋值
if (this == &another) {
return *this;
}
//2 先将自身的额外开辟的空间回收掉
if (this->name != NULL) {
delete[] this->name;
this->name = NULL;
this->id = 0;
}
//3 执行深拷贝
this->id = another.id;
int len = strlen(another.name); //strlen是求出字符串的有效长度,不包括结束符,故下面那句要有个+1
this->name = new char[len + 1];
strcpy(this->name, another.name);
//4 返回本身
return *this;
}
void printS()
{
cout << name << endl;
}
~Student() {
if (this->name != NULL) {
delete[] this->name;
this->name = NULL;
this->id = 0;
}
}
private:
int id;
char *name;
};
sprintf的使用:类型转换
sprintf函数原型为 int sprintf(char *str, const char *format, ...)。作用是格式化字符串,具体功能如下所示:
(1)将数字变量转换为字符串。
(2)得到整型变量的16进制和8进制字符串。
(3)连接多个字符串。
char str[256] = { 0 };
int data = 1024;
//将data转换为字符串
sprintf(str, "%d", data);
cout << str << endl;
string和int的转化时使用过istringstream,现在大致总结一下用法和测试用例。
介绍:C++引入了ostringstream、istringstream、stringstream这三个类,要使用他们创建对象就必须包含sstream.h头文件。
istringstream类用于执行C++风格的串流的输入操作。是由一个string对象构造而来,从一个string对象读取字符。
ostringstream类用于执行C++风格的串流的输出操作。 同样是有一个string对象构造而来,向一个string对象插入字符。
stringstream类同时可以支持C++风格的串流的输入输出操作。则是用于C++风格的字符串的输入输出的。
string test = "-123 9.87 welcome to, 989, test!"; //将字符串按空格分隔读取出来
istringstream sss(test);
string a;
while (sss >> a) {
cout << a << endl;
}
还可以用来进行类型转化string->int等等
stringstream stream;
string result = "10000";
int n = 0;
stream << result;
stream >> n;
cout << "n=" << n << endl;
反过来也ok
stringstream stream;
string result = "";
int n = 10000;
stream << n;
stream >> result;
cout << "result=" << result << endl;
注意:在进行多次转换的时候,必须调用stringstream的成员函数clear().
记得引入头文件 #include <sstream>
一个例子:
今天头条笔试的输入是用;隔开的
日常大家都用cin scanf空格分开的习惯了。这种;或者,的怎么处理呢,有个很简单的方法
先把一行读进来记录到str
把str里的;,都换成空格 str.replace
然后通过stringstream ss(str) 重新处理输入
例如 ss >> a >> b >> c
string s="he;is;a;good;boy";
string::size_type pos;
while ((pos = s.find(";")) != string::npos) {
s.replace(pos, 1, " ");//位置,欲替换的字符个数,替换的字符串
}
cout << s << endl;
string的一些比较:
首先给大家介绍一下getline()函数(个人觉得百度百科给的果断不够详细)
大家百度会发现getline()的原型是istream& getline ( istream &is , string &str , char delim );
其中 istream &is 表示一个输入流,譬如cin;string&str表示把从输入流读入的字符串存放在这个字符串中(可以自己随便命名,str什么的都可以);char delim表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为'\n',也就是回车换行符(遇到回车停止读入)。给大家举个例子:
string line;
cout<<"please cin a line:"
getline(cin,line,'#');
cout<<endl<<"The line you give is:"line;
那么当我输入"You are the #best!" 的时候,输入流实际上只读入了"You are the ",#后面的并没有存放到line中(应该是在缓冲区里吧)。然后程序运行结果应该是这样的:
please cin a line:You are the #best!
The line you give is:You are the
让我们一起来分析一下while(getline(cin,line))语句
注意这里默认回车符停止读入,按Ctrl+Z或键入EOF回车即可退出循环。
在这个语句中,首先getline从标准输入设备上读入字符,然后返回给输入流cin,注意了,是cin,所以while判断语句的真实判断对象是cin,也就是判断当前是否存在有效的输入流。在这种情况下,我想只要你的电脑不中毒不发神经你的输入流怎么会没有效?所以这种情况下不管你怎么输入都跳不出循环,因为你的输入流有效,跳不出循环。
然而有些同学误以为while判断语句的判断对象是line(也就是line是否为空),然后想通过直接回车(即输入一个空的line)跳出循环,却发现怎么也跳不出循环。这是因为你的回车只会终止getline()函数的读入操作。getline()函数终止后又进行while()判断(即判断输入流是否有效,你的输入流当然有效,满足条件),所以又运行getline()函数,所以,你懂了吧。。。
使用联合体union判断当前系统是大端还是小端
union 共用体类型名{
数据类型成员名;
数据类型成员名;
}变量名;
程序如下:
#include<iostream>
using namespace std ;
union TEST{
short a;
char b[sizeof(short)];
}
int main(){
TEST test;
test.a=0x0102; // 不能引用共用体变量, 只能引用共用体变量中的成员。
if(test.b[0]==0x01 && test.b[1]==0x02)
cout<<"big endian"<<endl;
else if(test.b[0]==0x02 && test.b[1]==0x01)
cout<<"small endian"<<endl;
else
cout< ” unknown ” < endl ;
return 0;
}
类模板:
有时,两个或多个类的功能是相同的,但仅仅因为数据类型不同,就可以定义一个类模板,再具体使用时才指定类型
如
template<class T>
class Operation {
public: //声明一个模板,虚拟类型名为T
Operation (T a, T b) :x (a) ,y(b) {}
Tadd(){
return x+y;
}
subtract(){
return x-y ;
}
private:
T x ,y;
};
声明一个类模板的对象时,要用实际类型名去取代虚拟的类型,这样才能使它变成一个实际的对象,如:
Operation <int> obj(1,2) ;
在类模板名之后的尖括号里指定实际的类型名,这样在编译时,编译系统就用int 取代类模板中的类型参数T ,这样就把类模板具体化了,或者说实际化了
其他
今天发生了一件非常丢脸的事情,在做OJ的时候,有道题目是事先不告诉你要输入几个数字,就是输入一行数字并用空格隔开,当时想了好久都没有解决这个输入的问题。。。
while(cin>>tmp){
}
这样一直尝试都是错的,后来在网上查了一下,才知道应该是这样:cin>>tmp是有返回值的,只要s满足类型条件,就会return true,一直执行下去,而cin会忽略空格或者enter,因此,enter后不会结束循环。
vector<int> res;
int tmp = 0;
while (cin>>tmp) {
res.push_back(tmp);
if (cin.get() == '\n')
break;
}
输入文件流,每行是一个字符串,到文件尾部EOF就输入结束
如果不是文件输入,而是通过键盘输入,则可以在输入结束后按ctrl+z,表示结束。此标记等同于文件中的结束标记。
vector<string>sss;
string s;
while (cin >> s && !cin.eof()) {
sss.push_back(s);
}
此外,如果用gets_(a,maxsize),遇到文件尾,也会返回0.
char a[100];
vector<string>sss;
while (gets_s(a, 100)) {
string s(a);
sss.push_back(s);
}
以空行结束的情况:
string s;
while(getline(cin,s)){
if(s.size()==0)
break;
}
stringstream:
输入用逗号分隔的字符串。
#include <string>
#include <sstream>
string s;
cin>>s;
stringstream ss(s);
vector<string> a;
string tmp;
while(getline(ss,tmp,",")){
a.push_back(tmp);
}
如果你打算在多次转换中使用同一个stringstream对象,记住再每次转换前要使用clear()方法