C 二维数组和指针、函数指针、typedef等的一些笔记
创建类的时候 用new 与 不用new 的区别
- 不使用
new
创建对象时,对象的内存空间是在 栈 中的,其作用范围只是在函数内部,函数执行完成后就会调用析构函数,删除该对象。 - 使用
new
创建对象,是创建在堆中的,必须要程序员手动的管理该对象的内存空间。 -
malloc()
函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc()
函数。new
与 malloc()
函数相比,其主要的优点是,new
不只是分配了内存,它还创建了对象。
类函数在 内部 和 外部 实现的区别
- 成员函数的实现可以在类定义时同时完成。
- 与类的定义相比, 在类内实现成员函数不再是在类内进行声明,而是直接将函数进行定义, 在类中定义成员函数时, 编译器默认会争取将其定义为 inline型函数
- 在类外定义成员函数通过在类内进行声明, 然后在类外通过作用域操作符
::
进行实现 - 转换为了“为啥不用全用内联?”的问题
构造函数的 显式声明 和 隐式声明 的区别
- 只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的。跟它相对应的另一个关键字是
implicit
, 意思是隐藏的。类构造函数默认情况下即声明为implicit
(隐式) - 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象
-
explicit
关键字的作用就是防止类构造函数的隐式自动转换 - 但是,当除了第一个参数以外的其他参数都有默认值的时候,
explicit
关键字依然有效, 此时, 当调用构造函数时只传入一个参数, 等效于只有一个参数的类构造函数
member access within misaligned address
在链表中,申请空间时代码如下:
temp1=(struct ListNode*)malloc(sizeof(struct ListNode));
由于结构体内存在 next
指针 ,而申请结构体空间后同时定义了 next
指针 ,此时 next
指针 未指向任何空间,故在测试时可能导致上述错误。
解决方法为:增加代码使 next
指针 指向空。
temp->next=NULL;
左值(Lvalues)和右值(Rvalues)
C++ 中有两种类型的表达式:
- 左值(lvalue):指向内存位置的表达式。左值可以出现在赋值号的左边或右边。
- 右值(rvalue):存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。
- 变量是左值,因此可以出现在赋值号的左边。
- 数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。
#include 带.h 和 不带.h 的区别
- 总结:
<string.h>
是旧的、不在std
中的 C/C++ 版本;<string>
是头文件的内容在std
中的 C++ 版本,对应的是新的string 类;<cstring>
是对应于旧的、但在std
中的 C 版本。(C库包含在C++标准库中) - 旧的C++头文件名如<iostream.h>,它的头文件的内容不在命名空间
std
中。 - 包装了
std
的那部分标准库创建的新头文件名,生成新头文件的方法仅仅是将现有C++
头文件名中的.h
去掉,如:<iostream>、<complex>; -
C
语言头文件标准化后,头文件名前带个c
字母,如<cstdio>、<cstring>、<ctime>、<ctype>; - 新的C++头文件如<iostream>包含的基本功能和对应的旧头文件相同,但头文件的内容在名字空间
std
中(在标准化的过程中,库中有些部分的细节被修改了,所以旧头文件和新头文件中的实体不一定完全对应)。 - 具有C库功能的新C++头文件具有如<cstdio>这样的名字。它们提供的内容和相应的旧C头文件相同,只是内容在
std
中。
异常处理
- 一个函数如果不交待能拋出哪些类型的异常,就可以拋出任何类型的异常。
- 函数不会拋出任何异常:
void func() throw ();
- 函数可能会拋出哪些异常:
void func() throw (int, double, A, B, C){...}
- 函数如果拋出了其异常声明列表中没有的异常,在编译时不会引发错误,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作用。
局部变量 与 全局变量 同名处理
局部变量与全局变量同名时,要使用全局变量:::a
# 和 ## 运算符
-
#
:字符串化的意思,出现在宏定义中的#
,是把跟在后面的参数转换成一个字符串。
- 当用作字符串化操作时,
#
的主要作用是将宏参数不经扩展地转换成字符串常量。 - 宏定义参数的左右两边的空格会被忽略,参数的各个 Token 之间的多个空格会被转换成一个空格。
- 宏定义参数中含有需要特殊含义字符如
"
或\
时,它们前面会自动被加上转义字符 \
。
#define MKSTR( x ) #x
cout << MKSTR(HELLO C++) << endl;
转换成了:
cout << "HELLO C++" << endl;
-
##
: 连接符号,把参数连在一起。将多个 Token 连接成一个 Token。要点:
- 它不能是宏定义中的第一个或最后一个 Token。
- 前后的空格可有可无。
#define CONCAT( x, y ) x ## y
int xy = 100;
cout << concat(x, y);
转换成了:
cout << xy;
pthread 和 std::thread 对比
-
std::thread
是C++11接口; -
pthread
是C++98接口,且只支持Linux。
std::thread
对比于pthread
的优缺点:
优点:
- 简单,易用
- 跨平台,pthread只能用在POSIX系统上(其他系统有其独立的thread实现)
- 提供了更多高级功能,比如future
- 更加C++(跟匿名函数,std::bind,RAII等C++特性更好的集成)
缺点:
- 没有RWlock。有一个类似的shared_mutex,不过它属于C++14,你的编译器很有可能不支持。
- 操作线程和Mutex等的API较少。毕竟为了跨平台,只能选取各原生实现的子集。如果你需要设置某些属性,需要通过API调用返回原生平台上的对应对象,再对返回的对象进行操作。
strcut 和 class 的区别
struct更适合看成是一个数据结构的实现体,class更适合看成是一个对象的实现体。struct和class关键字在C++中其基本语法是完全一样的。
C语言编程单位是函数,语句是程序的基本单元。而C++语言的编程单位是类。从c到c++的设计由以过程设计为中心向以数据组织为中心转移。
在C++中struct得到了很大的扩充:
- struct可以包括成员函数;
- struct可以实现继承;
- struct可以实现多态。
- 默认的继承访问权。class默认的是private,strcut默认的是public。
- 默认访问权限。struct作为数据结构的实现体,它默认的数据访问控制是public的,而class作为对象的实现体,它默认的成员变量访问控制是private的。
- class关键字还用于定义模板参数,就像“typename”。但关键字“struct”不用于定义模板参数。
不同容器使用的迭代器类型
容器 |
对应的迭代器类型 |
array |
随机访问迭代器 |
vector |
随机访问迭代器 |
deque |
随机访问迭代器 |
list |
双向迭代器 |
set / multiset |
双向迭代器 |
map / multimap |
双向迭代器 |
forward_list |
前向迭代器 |
unordered_map / unordered_multimap |
前向迭代器 |
unordered_set / unordered_multiset |
前向迭代器 |
stack |
不支持迭代器 |
queue |
不支持迭代器 |
注意,容器适配器 stack 和 queue 没有迭代器,它们包含有一些成员函数,可以用来对元素进行访问。
迭代器定义方式 |
具体格式 |
正向迭代器 |
容器类名::iterator 迭代器名; |
常量正向迭代器 |
容器类名::const_iterator 迭代器名; |
反向迭代器 |
容器类名::reverse_iterator 迭代器名; |
常量反向迭代器 |
容器类名::const_reverse_iterator 迭代器名; |
- 常量迭代器和非常量迭代器的分别在于:通过非常量迭代器还能修改其指向的元素。
- 反向迭代器和正向迭代器的区别在于:
- 对正向迭代器进行
++
操作时,迭代器会指向容器中的后一个元素; - 而对反向迭代器进行
++
操作时,迭代器会指向容器中的前一个元素。
左值引用& 和 右值引用&&
C++11右值引用(一看即懂)
- 可以取地址的,有名字的,非临时的就是左值;
- 不能取地址的,没有名字的,临时的就是右值;
- 左值引用要求右边的值必须能够取地址,如果无法取地址,可以用常引用;但使用常引用后,我们只能通过引用来读取数据,无法去修改数据,因为其被const修饰成常量引用了。
- 在c++中,临时对象不能作为左值,但可以作为常量引用const &。
- 右值只在当前表达式有效。右值性质:
- 没有名字
- 不可修改
x = plus(y, z) // plus(y, z)
x = (y+z) // (y+z)
- 右值引用是用来支持转移语义的。转移语义可以将资源 ( 堆,系统对象等 ) 从一个对象转移到另一个对象,这样能够减少不必要的临时对象的创建、拷贝以及销毁,能够大幅度提高 C++ 应用程序的性能。临时对象的维护 ( 创建和销毁 ) 对性能有严重影响。
- 右值引用用来绑定到右值,绑定到右值以后本来会被销毁的右值的生存期会延长至与绑定到它的右值引用的生存期。
冒号初始化 和 构造函数内赋值
通常我们对类成员进行“初始化”有两种方式:
- 构造函数后面跟冒号
:
=> 系统创建成员变量时就初始化。初始化这个数据成员的时候函数体还未执行。 - 构造函数里面对成员进行赋值 => 系统创建成员变量,创建完后再进行赋值操作。是在所有的数据成员被分配内存空间后才进行的。
根据C++的规则,const类型和引用不可以被赋值,只能被初始化。
class A
{
public:
A(int& c) {
_a = 1;
}
protected:
int _a;
// 报错,成员_b和_c必须在构造函数的成员初始化列表里面初始化
const int _b;
int& _c;
};
应改为,在构造函数后面通过冒号,使用括号表达式进行初始化:
class A
{
public:
A(int& c): _b(2), _c(c) {
_a = 1;
}
protected:
int _a;
const int _b;
int& _c;
};
单冒号: 与 双冒号: 的区别
单冒号:
- 继承
class Base { };
class Derived : public Base { };
class Base {
public:
int a=10;
};
class Derived : public Base {
};
int main() {
Derived b;
cout<<b.a<<endl;
}
- 访问控制
class MyClass {
public:
void public_member() { }
private:
void private_member() { }
};
- 列表初始化
class MyClass {
private:
int member;
public:
MyClass() : member(0) { }
};
- 位域(即该变量占几个bit空间)
class MyClass {
public:
unsigned first : 1;
unsigned second : 2;
unsigned third : 4;
};
双冒号::
- 域操作符 => 全局作用域(::name)、类作用域(class::name)、命名空间作用域(space::name)
1、没有在类的声明里给出f的定义,那么在类外定义f时,要写成void A::f(),表示这个f()函数是类A的成员函数。
2、直接使用类调用静态类成员函数。
vector列表初始化{} 和 元素数量()
- 在某些情况下,初始化的真实含义依赖于传递初始值时用的是花括号
{}
还是圆括号()
。
vector<int> v1(10); // 有10个元素,每个的值都是默认初始化0
vector<int> v2{10}; // 有1个元素,且值是10
vector<int> v3(10, 1); // 有10个元素,每个的值都是1
vector<int> v4{10, 1}; // 有2个元素,分别是10和1
- 如果用的是圆括号,可以说提供的值是用来构造 vector对象的。如果用的是花括号,可以表述成我们想列表初始化该vector对象。
- 另一方面,如果初始化时使用了花括号的形式,但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了。
vector<string> v5{"hi"}; // 列表初始化,有一个元素
vector<string> v6("hi"); // 错误,不能使用字符串字面值构建vector对象
vector<string> v7{10}; // 有10个默认初始化的元素
vector<string> v8{10, "hi"}; // 有10个值为"hi"的元素
- 确认无法执行列表初始化后,编译器会尝试用默认值初始化vector对象。
- 总结:
(a,b)
用来说明有a个b值,{a,b,c}
表示用列表abc来初始化,当{}
中内容跟vector内类型不同时就尝试替换为()
的规则。还是不要乱用吧,太混乱了…
其他
- 一个const成员函数如果以引用的形式返回
*this
,那么他的返回类型将是常量引用。
// 如果display返回常量引用,则调用set将引发错误
screen.display(cout).set('*');
- 编译器处理完类中的全部声明后,才会处理成员函数的定义。
- 类型名的定义通常出现在类的开始处,这样就能确保所有使用该类型的成员都出现在类名的定义之后。
- 如果类的成员是const、引用,或属于某种未提供默认构造函数的类类型,我们必须通过构造函数初始值列表为这些成员提供初值。
- 类成员的初始化顺序与它们在类定义中的出现顺序一致。最好令构造函数初始值的顺序与成员声明的顺序保持一致。
- 如果一个构造函数为所有参数都提供了默认实参,则它实际上也定义了默认构造函数。
- 关键字
explicit
只对一个实参的构造函数有效。需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的。只能在类内声明构造函数时使用explicit
关键字,在类外部定义时不应重复。 - 当在类的外部定义静态成员时,不能重复使用
static
关键字,该关键字只出现在类内部的声明语句。