引言
C++ primer plus的知识笔记,以下内容基于c++11标准
&在c++中的一种作用为取出当前变量在内存的逻辑地址 *在c++中的一种作用为取出当前逻辑地址对应的内存空间的值 c++中提供指针变量存储对象的地址,指针的运算会被编译器优化为地址的运算,比如一个int类型的指针 p+1的值实际是p指向的地址+1个int所占空间大小后的逻辑地址 指针语法为 typename * variable,比如
int a = 5;
int * p = &a;
*p == 5;//true
而对于结构体指针,可以使用->来操作成员,如
struct data{
int a = 1;
}
data d1 = {
a:2
};
data * dp1 = &d1;
d1.a = 3;
dp1->a = 5;
(*dp1).a = 6;
c++中可以在文件头使用#define进行宏定义,编译器在编译时会将代码里特定字符串替换为宏定义以后的结果,这也是内联函数的实现原理(第6版书中255页,8.1小节),所以内联函数会比较占据内存(多个代码副本),也就是调用内联函数的地方,会被编译器替换为内联函数的执行代码,与宏定义的函数区别是,内联函数更加的严谨,其限定了参数类型及返回值类型 比如
#define eetal 1
int a = eetal;//compile as int a = 1;
#define sum(x,y) (x+y)
int b = sum(1,2);//compile as int b = (1+2)
inline int sum(int a,int b){
return a+b;
}
因为宏定义会对文本替换,一般用于定义常量等,为了避免重复宏定义,c++提供了#ifndef(if not define缩写)命令来判断当前是否进行了某个名称的宏定义,可以根据结果进行处理 如:
#ifndef eetal
//code
#endif
同时还有用于判断宏定义的#if
#define a 5
#if a>5
...
#endif
以上代码代表如果没有定义过eetal这个宏变量,则会执行#ifndef和最近的endif之间的代码
与宏定义类似的,c++还提供了typedef可以对类型取别名和定义一些函数指针的别名 比如
typedef int iint;
typedef void (*functionPointer)(int a);//接下来代码中
在上述代码之后的代码中使用iint定义变量等价于使用int 使用functionPointer作为类型可以定义指向 返回值为void,只有一个int形参的函数指针 c++11标准给using指令也加入了取别名的用法(真希望可以废弃一些旧的用法,太多重复的东西)。上述代码等价于
using iint = int;
using functionPointer = void (*)(int a);
c++的泛型通过template来设定,泛型方法代表该方法尚未注册实际代码,只有在代码里调用了该方法时,会通过隐式触发或者显示定义或者主动触发来创建对应的方法实例 方法的匹配规则为 代码中指定泛型方法>普通方法>显示声明>泛型方法 如
//在一些标准好像typename也会写为class,如下的两种写法swap01和swap02一样
template<class T> void swap02(T &a,T &b){
T temp = b;
b = a;
a = temp;
}
template<typename T> void swap01(T &a,T &b){
T temp = b;
b = a;
a = temp;
}
//泛型的显示声明,匹配规则(代码中指定泛型方法>普通方法>显示声明>泛型方法)
template<> void swap01<int>(int &a,int &b);
此时调用如
int a,b;
swap01(a,b);
//普通调用,先匹配显示声明
double c,d;
swap01(c,d);
//显示声明不匹配类型,触发隐式生成double泛型的方法实例匹配
char e,f;
swap01<char>(e,f);
//主动指定方法泛型为char
因为泛型会导致方法的不安全,比如
template<typename T> void sum(T &a,T &b){
? c = a+b;
//do something
}
因为c++重载了运算符,string类型也可以使用+拼接,那如何确定泛型变量运算返回值的类型呢?于是有了decltype
decltype(a+b) c = a+b;
上述代码代表,如果a+b的表达式合法,c的类型即为他们运算后值的类型,deltype还有一个特殊用法来创建引用
int a;
decltype((a)) sa = a;//equals to int & sa = a;
上述代码代表sa的类型为 a的类型 的引用类型,即变量sa此时是a的一个引用
const代表常量定义(java里的final),不可修改。因为cpp里面的指针类型定义比较特殊,所以const的位置代表不同含义,如
const int v = 0;//v cantnot be change anymore
struct data{
int a;
};
....
data t1 = {
a = 0;
},t2 = {
a = 3;
};
const data * p1 = &t1;
//p1->a = 4 cannot work, error
//p1 = p2 it work
data * const p2 = &t2;
//p2->a = 4 it work
//p2 = p1 cannot work, error
如上述代码,p1可以修改指向的地址,但是无法通过p1修改单元内容 p2不可修改指向的地址,但是可以通过p2修改单元内容
mutable用于标记结构体中不想受结构体的const限制的成员,比如
const struct data{
int a = 1;
mutable int b = 0;
}
data * d = new data();
d->b = 3;//ok
//d->a = 5;error
上述代码创建了一个结构体data,因为其以const修饰,成员a不能被修改,而因为成员b以mutable修饰,不受限制可以修改
与java一样消除内存屏障用的,标记告诉编译器不要缓存该变量到寄存器或者线程缓存,每次从内存读取
volatile int a;
用于创建存放在当前线程作用域的对象,该对象存放在当前线程内存,在当前线程存活时间里保持存活
thread_local int a = 1;
在c++11标准里代表默认,类似java的default(在以前标准为通知编译器尽量存储到寄存器,不过c++11改了)
在c++11标准代表类型推断,必须在定义时为变量完成初始化,否则无法推断(在旧版本标准为默认,即当前的register作用)
auto a = 1;
//auto b; error
在c++中,在文件的函数外部,及全局便写的变量为全局变量,其他连接的工程文件在代码里可以通过extern关键字引入其他文件的全局变量。 而static代表标记变量为静态的全局变量,不能被extern发现 全局变量的生命周期时整个程序运行期间
//1.cpp
int A = 100;
static int B = 5;
、
//2.cpp
extern int A;//100
//extern int B; error
c++的枚举存储整数,不指定默认第一个为0后续每个依次递增,整数不能直接赋值给枚举变量,需要通过构造,而枚举变量可以直接赋值给整数
enum langs{java, cpp};// 0 and 1
//enum langs{java=3, cpp=100};// 3 and 100
langs newLang = langs(1);//cpp
langs anotherLang = java;
int howMuch = anotherLang;//0
cpp中也是使用new来创建对象(在cpp中比较习惯称为分配内存,c里还有malloc,calloc,realloc),同时提供释放内存的指令,这会造成很多危险,但是也带来性能的提升
#include<new>
int * p = new int();//只申请一个int的空间,返回地址
int * ap = new int[5];//申请一个5个int元素的数值空间,返回第一个元素地址
int * p2 = new(p) int();//指定要分配空间的首地址,此种方式要引入new头文件
如上代码,其中ap为数组头,而p2指定了首地址为p1,这样代表其分配的空间覆盖了p1的空间,当尝试申请的内存不够时,在c++11中会抛出异常(以前是返回空地址0)
对于普通指针,使用delete 指针删除,而对于数组类型的指针,应当使用delete[]来调用数组各个元素的析构函数(基本类型只是释放),因为p1已经被p2覆盖,所以释放了p2就不能再去释放p1,因为内存已经被回收了
delete p2;
delete[] ap;
c++因为大部分代码还是存在面向过程,放在全局的变量的做法,为了方便管理避免重名,引入命名空间,通过命名空间归类方法和变量,比如
namespace std{
istream cin;
ostream cout;
}
上述代码创建了一个std的命名空间,里面的对象通过 命名空间::成员名称 使用,比如
std::cin;
std::cout;
通过使用using命令,指定将命名空间内容加入当前代码块,则可以省略命名空间,如
using namespace std;
cin;
cout;
命名空间还可以嵌套
namespace yyt{
namespace std{
istream cin;
ostream cout;
}
}
....
yyt::std::cin;
对于本文件的全局变量,可以使用匿名命名空间访问成员,如
#include<iostream>
namespace eetal{
int a = 10;
}
int a = 5;
int main(){
int a = 3;
std::cout<<a<<endl;//3
std::cout<<::a<<endl;//5
std::cout<<eetal::a<<endl;//10
return 0;
}
文件IO需要通过引入头文件fstream
#inlcude<fstream>
//输出流ofstream
ofstream outPutStream;
outPutStream.open("test.txt");//will clear old content
outPutStream.write("aaas",4);//writer max 4 characters
outPutStream.flush();
outPutStream.close();
、
//输入流ifstream,
ifstream inputStream;
inputStream.open("test.txt");
char str[5];
inputStream.read(str,4);//read max 4 characters
inputStream.close();
array是有界的,vactor无界(自动增长),并且可以直接像数组一样使用
#include<array>
#include<vector>
int size = 5;
vector<double> doublevWithSize(size);//size is a variable
array<int,5> array1;//cannot use variable the size must is const
doublevWithSize[0] = array1[0];