C++/C学习笔记

前排指引:

volatile:C/C++ 中的 volatile - 知乎

lambda表达式实现原理:C++ Lambda 编译器实现原理 - 链滴

operator关键字作为类型转换符的使用:C++ operator关键字详解 - 寒魔影 - 博客园

关于同步/异步与阻塞/非阻塞的区别:https://www.zhihu.com/question/19732473

 句柄:C++ 什么是句柄?为什么会有句柄?HANDLE - vinsonLu - 博客园

typedef详解:C/C++ typedef用法详解(真的很详细) - 智者无惧 - 博客园

智能指针:详解C++11智能指针 - WindSun - 博客园

C++全排类函数:https://www.cnblogs.com/cstdio1/p/11311500.html

匿名函数:C++匿名函数的使用 - 风雅yaya - 博客园

C++和C#对比:C++和C#对比_TQT的博客-CSDN博客

C++string中find,find_first_of和find_last_of的用法https://www.cnblogs.com/zh20130424/p/11099932.htmll

指针数组、数组指针和函数指针等概念的理解:C 语言中指针数组和数组指针、函数指针、指向函数的指针等等该怎么理解? - 知乎

C++各种初始化:C++的各种初始化方式 - impluse - 博客园

类和结构各种初始化写法和不同:类和结构初始化的写法及不同_TQT的博客-CSDN博客

C++构造函数:C++构造函数详解 - 范仁义 - 博客园

再补充一个移动构造函数:C++11移动构造函数详解

C++从源码到可执行程序的过程:编译与静态链接_taocr的博客-CSDN博客_静态编译和静态链接

静态链接和动态链接:动态链接和静态链接_TQT的博客-CSDN博客

头文件包含问题:C++头文件包含的问题_TQT的博客-CSDN博客

include详解:C/C++ #include详解_skywf的博客-CSDN博客_c++include

include简单总结:当预处理器发现#include 指令时,会查看后面的文件名并把文件的内容包含到当前文件中,即替换源文件中的#include指令,这相当于把被包含文件的全部内容输入到源文件#include指令所在的位置

名称空间:C++名称空间基本使用_TQT的博客-CSDN博客

push_back和emplace_back比较:C++_STL_容器_pushback VS emplace_back的比较,以后都用emplace系列函数吧_哔哩哔哩_bilibili

C++Socket的shutdown和close方法:【C/C++】socket关闭close和shutdown (备忘)_一棵草Telen_新浪博客


基本:

explicit关键字:禁止隐式类型转换

注意事项:
1.explicit关键字只能用于类内部的构造函数声明上
2.explicit 关键字作用于单个参数的构造函数

#include <iostream>

class Circle
{
public:
    //隐式
    Circle(double r) :R(r) {}
    Circle(const Circle& c) :R(c.R), X(c.X), Y(c.Y) {}
    //显式
    /*explicit Circle(double r):R(r) {}
    explicit Circle (const Circle& c) :R(c.R), X(c.X), Y(c.Y) {}*/
private:
    double R;
    int X, Y;
};

int main()
{
    //隐式
    Circle A = 1.23;
    Circle A = Circle(1.23);
    Circle C = A;
    //显式
    /*Circle A(1.23);
    Circle C(A);*/
}

extern关键字:

在同一文件中,可以在上文引用下文中出现的变量或方法
如:
变量:
#include<stdio.h>
int main()
{
    extern int num;
    num = 0;
    printf("%d\n", num);
    return 0;
}
int num;
方法:同变量,但其实在同一文件中我们一般通过在上文声明下文定义的方法来解决

不同文件中,可以在当前文件引用另一文件使用的方法和变量
如:
变量:
1.c
#include<stdio.h>
int main()
{
    extern int num;
    num = 0;
    printf("%d\n", num);
    return 0;
}
2.c
int num;

注意:
extern引用变量时变量必须是全局变量
extern如果在某个方法中引用变量或者方法,则该变量和方法的作用域只限于当前方法体内
extern引用变量时不能赋值,引用完毕后可以赋值
extern所引用的变量不能是常量,原因是常量在声明时必须赋值,而extern关键字不允许声明时的赋值

补充:
VC++中在使用extern时可以赋值,但这样会使extern失去原来的作用,其它环境未测试
extern使用时的几个注意事项:https://www.cnblogs.com/uestc-wxp/articles/2638291.html
Static关键字:
用Static关键字修饰的变量全部放在静态(全局)存储区
静态全局变量:相对于全局变量,静态全局变量的活动范围只限于当前文件,extern不可引用
静态局部变量:相对原来的局部变量,静态局部变量在当前函数调用完毕后并不会被释放,因此当下次函数调用时该变量的值仍然为上次的值,但不能在该函数以外的地方使用

使用上述三种方式对于没有明显指定值的元素,若数组尾全局变量或者静态变量,则该元素的值为0,若为局部变量则为随机值

二维数组:

 

运算符重载:

#include <iostream>

using namespace std;

class Test
{
public:
	int x;
	Test(int xValue, int yValue) : x(xValue), y(yValue) {}
	bool operator< (const Test& t) const //成员函数这样写
	{
		//return this->y < t.y; //y私有,可以这样用
		return this->x < t.x;
	}
private:
	int y;
	/*bool operator< (const Test& t) const //私有在类外不能用
	{
		return this->x < t.x;
	}*/
};

bool operator> (const Test& t1, const Test& t2) //非成员函数这样写
{
	//return t1.y > t2.y; //因为y是私有的,所以这里不能这样用
	return t1.x > t2.x;
}

int main()
{
	Test t1 = Test(-1, -2), t2 = Test(1, 2);
	//cout << (t1 > t2) << endl;
	//cout << (t1 == t2) << endl; //==没有定义
	cout << (t1 < t2) << endl;
	return 0;

}

枚举类型的使用见:

C++枚举类型用法总结(enum)(可以用枚举字符常量代替常量)_净无邪博客-CSDN博客_c++枚举类型enum用法

一点补充:

非枚举类可以加枚举名,但只是方便调用(用::),拥有不同枚举名的不同枚举类型其成员并不能相同


STL:

vector和pair可以做map和multimap的键,因为它们有默认的比较运算符,但不能做unorderedmap的键,因为它们是不可哈希的,另外注意multimap与map不同不支持下标运算

vector和pair可以加入到set中,因为它们有默认的比较运算符,但不能加入到unorderedset中,因为它们不可哈希
(注:set、map、multiset、multimap都是红黑树实现,所以要求能比较大小,而unorderedset和unorderedmap都是哈希表实现,所以要求可以哈希)

vector和pair可以加入到栈和队列中(注:栈和队列都是泛型数组实现的,所以对元素没有什么要求)

STL使用注意事项:

对于map和unordered_map,可以查看满足指定类型的任何键所对应的值,即使该键没有添加过,此时键所对应的值为0

对于map的insert方法,如果插入的数据已经存在,则不会插入,即不能使用该方法完成覆盖更新

set(multiset)如果存储的是自定义类型的数据(已重定义小于运算符),那么可以理解为该数据涉及的比较操作都会由小于运算符中指定的成员所代表


友元函数:

1.友元函数即在一个非成员函数权限的基础上增加了其访问私有成员的权限,但由于友元函数常常在类内声明类外定义,所以在访问类内成员时(无论公有还是私有,静态还是非静态)应加类限定符(.、::等)

2.在类内声明时友元函数私有和公有行为一致

3.尽量不要在类内定义的友元函数,这种函数只能在类内用,有点像私有成员函数(实际可能不同,先这样理解)


字符串:

C++中用C的方式创建字符串时的一个注意点:

char str[5] = "abcd";

中括号中虽然是5,但后面最多放四个字符,因为还有一个\0会自动加在后面(C语言中不会自动加,因此需要我们自己注意),这点可以通过不在中括号中加数字来回避,此时字符数组的长度由编译器根据字符串的值自动识别

C++string使用时要加上string头文件,另外iostream头文件中引用了string

C++和C#String是否是'\0'结尾:

C++中没有规定string以'\0'结尾,但大部分编译器在实现时会为string结尾增加'\0'

C#string并不以'\0'结尾

C++同C一样,char数组最后一个必须是空字符,而C#不需要

在C++和C中,字符数组和string都可以修改,C#不能

string和C风格字符串常用输入方式及相关函数解析:https://blog.csdn.net/qq_38234381/article/details/120904698


指针和引用:

一块内存空间被释放之后这块内存空间的值可能保持原样也可能可变,指向的指针也是,这取决于编译器的实现

同一块内存空间不能被释放多次,对于这种错误很多编译器都会报错,但也有不会的,其真正原因在于如果一个内存空间被释放了,就不应该有任何的指针能够访问它(释放时是通过指针来释放的),因为一旦存在这样的指针,那么当被释放的内存空间重新分配后,该指针仍能访问该空间,这是不安全的

空指针:指向内存地址为0的指针,如

int * p = NULL;

野指针:指向非法地址(如未经申请的地址)的指针,如

int * p = (int*)0x123456789;

上面两种指针都可以用if来判断指向,但对于指针所指地址中的数据既不能读也不能写

32位操作系统,指针占用4个字节

64位操作系统,指针占用8个字节

关于指针和引用访问非法内存的一点讨论:

1.不能返回一个临时变量的引用(或不能返回一个指向临时变量的指针),尽管运行结果并没有出错:

//一部分情况,其余情况可自推
int & clone(int & num1)
{
	int * p = new int;
	*p = num1;
	//注意这里不是返回局部变量指针的引用,而是返回指针所指变量的引用(尽管该变量没有名字,只能通过指针调用)
	return *p;

	//不能返回局部变量的引用
	/*int tmp = num1;
	return tmp;*/

	//与上面相同,都返回了局部变量的引用,指针只不过是换了一种调用的形式,最终调用的变量的都是tmp
	/*int tmp = 1;
	int* p = &tmp;
	return *p;*/
}

2.不要用下面的方法访问非法内存,尽管运行结果并没有出错:

#include<iostream>

using namespace std;

int * p;

void Test()
{
	int tmp = 11;
	p = &tmp;
}

int main()
{
	Test();
	cout << *p;
}

总结:

对于访问已经失效的内存这种情况,C++标准没有明确规定要表现出怎样的行为。正规的说法是:对此行为,未定义(undefined)

未定义是什么意思呢?也许在这个系统上是好的,而在另外一个系统就崩溃了;也许在相同系统的这台机器是好的,在另一台机器就崩溃了;也许在调试的时候是好的,发布到客户那里去就崩溃了;也许你得到上天的眷顾,从来都没有崩溃过

数组名和指针的区别:

(1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组(如对一个数组名使用sizeof会得到数组的大小)

(2)数组名的外延在于其可以转换为指向其成员类型的指针,而且是一个指针常量(如数组名的使用和指向数组第一个元素的指针的使用基本一致,但数组名不可自增和自减)

(3)指向数组的指针则是另外一种变量类型(可以通过对一个数组名取址得到该类型的变量),仅仅意味着数组的存放地址,并不是数组名

补充:

1.当将数组名作为函数参数时,此时数组名完全等价为指向其第一个元素的指针

2.使用指针指向new[]分配的地址,相当于用指针指向了数组的第一个元素的地址,显然此时与数组名不等价

参考:C/C++数组名与指针区别深入探索(转载) - 冷冰若水 - 博客园

指向空类型的指针可以指向任何类型的变量,而指向某一类型的指针只能指向特定类型的变量,上述两种指针均可以强制转换为指向其它类型的指针


关于继承:

C++中基类的构造函数不会被继承,常常在派生类的构造函数中使用成员列表初始化显示调用基类构造函数(注意只有在这一个地方调用基类构造函数才可以派生类所继承的基类部分其它地方调用基类构造函数都与在相当于构造一个新的基类对象),否则将会调用基类的无参构造函数,如果基类没有无参构造函数,则会报错(C#同理)

子类如果重新定义了父类的公有非虚同名方法,则该方法将隐藏父类的原方法(可以通过作用域运算符调用隐藏的方法),但是如果一个子类的上转型对象调用该方法时则不会调用子类中的版本

私有继承:

①基类和派生类之间建立的has-a关系:基类的公有成员和保护成员都将成为派生类的私有成员

②初始化时在构造函数中用成员初始化列表进行初始化

③调用基类方法:基类名加作用域解析运算符加方法名

④访问基类对象本身:直接显示转换(只能在派生类内)

⑤访问基类的友元函数(用类名显示地限定函数名不合适于友元函数,因为友元函数不属于类):将派生类显式转换为基类然后调用

⑥在类外通过派生类访问基类方法(私有继承):在派生类中使用using声明一下即可。例如:

using std::valarray<double>::min;

详见C++PrimerPlusP449上

基类指针可以指向派生类对象的地址
基类引用可以指向派生类对象的引用
基类对象不能用派生类对象赋值


时间:

tm结构体:在标准C/C++中,我们可通过tm结构来获得日期和时间,tm结构在time.h中定义,关于tm结构体:

struct tm 
{  
int tm_sec;		 /* 秒–取值区间为[0,59] */   
int tm_min; 		 /* 分 - 取值区间为[0,59] */   
int tm_hour; 	         /* 时 - 取值区间为[0,23] */   
int tm_mday;		 /* 一个月中的日期 - 取值区间为[1,31] */  
int tm_mon;		 /* 月份(从一月开始,0代表一月) - 取值区间为[0,11] */ 
int tm_year; 	         /* 年份,其值从1900开始 */  
int tm_wday; 	         /* 星期–取值区间为[0,6],其中0代表星期天,1代表星期一,以此类推 */  
int tm_yday; 	         /* 从每年的1月1日开始的天数–取值区间为[0,365],其中0代表1月1日,1代表1月2日,以此类推 */  
int tm_isdst; 	         /* 夏令时标识符,实行夏令时的时候,tm_isdst为正。不实行夏令时的进候,tm_isdst为0;不了解情况时,tm_isdst()为负。*/  
long int tm_gmtoff;	 /*指定了日期变更线东面时区中UTC东部时区正秒数或UTC西部时区的负秒数*/   
const char *tm_zone;     /*当前时区的名字(与环境变量TZ有关)*/  
}; 

localtime:将time_t转换为tm结构体,详见:http://www.cplusplus.com/reference/ctime/localtime/?kw=localtime

clock和time的区别:clock返回从程序运行到现在的毫秒数,time在没有传参的情况下返回从1970.1.1到现在的总秒数,传参则参数的值等于返回值


C++多线程(高级编程是Linux下的;这里是C++11的新标准):

创建多线程:

#include<iostream>
#include<thread>
using namespace std;
void proc(int a)
{
    cout << "我是子线程,传入参数为" << a << endl;
    cout << "子线程中显示子线程id为" << this_thread::get_id() << endl;
}
int main()
{
    cout << "我是主线程" << endl;
    int a = 9;
    thread th2(proc, a);//第一个参数为函数名,第二个参数为该函数的第一个参数,如果该函数接收多个参数就依次写在后面。此时线程开始执行。
    cout << "主线程中显示子线程id为" << th2.get_id() << endl;
    th2.join();//此时主线程被阻塞直至子线程执行结束后才继续执行。
    return 0;
}

注意一个子线程执行完后就会被主线程销毁,所以不能再次join

detach开启的线程为后台线程,即主线程不会阻塞等待它,而是一起执行,当主线程执行完毕无论开启的子线程是否执行完都会结束当前程序


锁(高级编程部分是Linux下的,对锁的支持比较完备,不仅有互斥锁,还有自旋锁、读写锁等;而这里是C++11的新标准,只有互斥锁,但可以自己实现自旋锁和读写锁等;关于上述提到的各种锁的区别见请你讲述一下互斥锁(mutex)机制,以及互斥锁和读写锁的区别_c++校招面试题目合集_牛客网):

互斥锁:

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
    m.lock();
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
    m.unlock();
}

void proc2(int a)
{
    m.lock();
    cout << "proc2函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 1 << endl;
    m.unlock();
}
int main()
{
    int a = 0;
    thread proc1(proc1, a);
    thread proc2(proc2, a);
    proc1.join();
    proc2.join();
    return 0;
}

lock_guard:

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
    lock_guard<mutex> g1(m);//用此语句替换了m.lock();lock_guard传入一个参数时,该参数为互斥量,此时调用了lock_guard的构造函数,申请锁定m
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
}//此时不需要写m.unlock(),g1出了作用域被释放,自动调用析构函数,于是m被解锁

void proc2(int a)
{
    {
        lock_guard<mutex> g2(m);
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }//通过使用{}来调整作用域范围,可使得m在合适的地方被解锁
    cout << "作用域外的内容3" << endl;
    cout << "作用域外的内容4" << endl;
    cout << "作用域外的内容5" << endl;
}
int main()
{
    int a = 0;
    thread proc1(proc1, a);
    thread proc2(proc2, a);
    proc1.join();
    proc2.join();
    return 0;
}

使用adopt_lock标识表示构造函数中不再进行互斥量锁定,因此此时需要提前手动锁定

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;//实例化m对象,不要理解为定义变量
void proc1(int a)
{
    m.lock();//手动锁定
    lock_guard<mutex> g1(m,adopt_lock);
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
}//自动解锁

void proc2(int a)
{
    lock_guard<mutex> g2(m);//自动锁定
    cout << "proc2函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 1 << endl;
}//自动解锁
int main()
{
    int a = 0;
    thread proc1(proc1, a);
    thread proc2(proc2, a);
    proc1.join();
    proc2.join();
    return 0;
}

unique_lock:

std::unique_lock类似于lock_guard,只是std::unique_lock用法更加丰富,同时支持std::lock_guard()的原有功能

使用std::lock_guard后不能手动lock()与手动unlock();使用std::unique_lock后可以手动lock()与手动unlock()

std::unique_lock的第二个参数,除了可以是adopt_lock,还可以是try_to_lock与defer_lock

try_to_lock:新创建的unique_lock对象管理Mutex对象m,并尝试调用m.try_lock()对Mutex对象进行上锁,但如果上锁不成功,并不会阻塞当前线程

defer_lock:新创建的unique_lock对象管理Mutex对象m,但是在初始化的时候并不锁住Mutex对象(之后可能需要手动锁)。m应该是一个没有被锁住的Mutex对象

#include<iostream>
#include<thread>
#include<mutex>
using namespace std;
mutex m;
void proc1(int a)
{
    unique_lock<mutex> g1(m, defer_lock);//始化了一个没有加锁的mutex
    cout << "xxxxxxxx" << endl;
    g1.lock();//手动加锁,注意,不是m.lock();注意,不是m.lock(),m已经被g1接管了;
    cout << "proc1函数正在改写a" << endl;
    cout << "原始a为" << a << endl;
    cout << "现在a为" << a + 2 << endl;
    g1.unlock();//临时解锁
    cout << "xxxxx"  << endl;
    g1.lock();
    cout << "xxxxxx" << endl;
}//自动解锁

void proc2(int a)
{
    unique_lock<mutex> g2(m,try_to_lock);//尝试加锁一次,但如果没有锁定成功,会立即返回,不会阻塞在那里,且不会再次尝试锁操作。
    if(g2.owns_lock){//锁成功
        cout << "proc2函数正在改写a" << endl;
        cout << "原始a为" << a << endl;
        cout << "现在a为" << a + 1 << endl;
    }else{//锁失败则执行这段语句
        cout <<""<<endl;
    }
}//自动解锁

int main()
{
    int a = 0;
    thread proc1(proc1, a);
    thread proc2(proc2, a);
    proc1.join();
    proc2.join();
    return 0;
}

std::unique_lock所有权的转移:

这里的转移指的是std::unique_lock对象间的转移

mutex m;
{  
    unique_lock<mutex> g2(m,defer_lock);
    unique_lock<mutex> g3(move(g2));//所有权转移,此时由g3来管理互斥量m
    g3.lock();
    g3.unlock();
    g3.lock();
}
//调用move后,原对象就如同通过默认构造函数所创建的,也就不再管理任何Mutex对象了

参考:

C++多线程并发基础入门教程 - 知乎

C++11 并发指南系列 - Haippy - 博客园

介绍 - 《C++ 并发编程实战 第二版 (C++ Concurrency in Action - SECOND EDITION)》 - 书栈网 · BookStack(圣经)


(下面是Linux下C++高级编程的部分,除特殊说明外都参考了http://www.freecplus.net/index.html)

网络操作:

网络基本操作见C语言技术网

socket的第三个填0表示自动选取合适的协议

htons htonl ntohs ntohl:分别用来实现将16位的无符号主机字节序转换为网络字节序,将32位的无符号主机字节序转换为网络字节序,将16位的无符号网络字节序转换为主机字节序,将32位的无符号网络字节序转换为主机字节序,以方便不同主机或同一主机的不同进程之间的通信

atoi:将字符串转换为整数

inet_addr:将一个点分十进制的IP(字符串)转换为u_long类型数(网络序)

perror:https://baike.baidu.com/item/perror/6828989?fr=aladdin

accept:从已准备好的连接队列中获取一个请求,如果队列为空,accept会阻塞等待,listen的第二个参数表示连接队列的最大长度

详见:https://blog.csdn.net/jirryzhang/article/details/76896360

netstat命令:https://www.cnblogs.com/ggjucheng/archive/2012/01/08/2316661.html

常用:netstat -na(用来查看socket,可以配合grep筛选和more分页查看)

当发送缓冲区满了之后send也会阻塞

setsocket和getsocket:获取或者设置与某个套接字关联的选项。当操作套接字选项时,选项位于的层(参数二)和选项的名称(参数三)必须给出。例如,为了表示一个选项由套接字协议解析,应该将层的值指定为SOL_SOCKE,为了表示一个选项由TCP协议解析,应该将层的值指定为IPPRO_TCP

详见:https://blog.csdn.net/renchunlin66/article/details/52351673,关于上面所说的层:https://blog.csdn.net/yaopeng_2005/article/details/7064869

其它:网络截图、服务器和客户端程序(多进程和多线程)


IO同步:

Select函数基本:https://www.cnblogs.com/alantu2018/p/8612722.html

Select函数之所以要传副本的fdset是因为Select会改变fdset,即只保留其中有事件发生的fd
监听套接字既可以放入readfdset也可以放在writefdset,该fd的事件(当有其它客户端连接过来时)也会导致Select取消阻塞

除此之外无论是readfdset还是writefdset,当有客户端断开连接时Select同样取消阻塞(属于客户端fd的事件)

其它:Select相关截图及代码

poll基本:https://blog.csdn.net/skypeng57/article/details/82743681

其它:Poll截图和相关代码

epoll:参考截图和代码


服务器性能测试:

free命令(用来查看内存占用情况):https://www.runoob.com/linux/linux-comm-free.html

top:查看CPU占用情况

Linux下的内存交换区和Windows下的虚拟内存、内存交换区:

虚拟内存:Linux下的内存交换区和Windos下的虚拟内存(如pagefile.sys等)是一回事

Windos下的内存交换区:Win8+的swapfile.sys文件,是为了给Metro应用(Win10为UWP应用)

被挂入后台时强制待机所用。系统会在挂起的短暂时间后(几十秒)将应用的内存写入该文件以腾

出物理内存,而在重新打开时从该文件读取整个应用的虚拟内存空间而直接打开。因为是连续读

取,所以HDD也不会很慢,并且省去了初始化时间、保存了应用状态,类似Win8+的“快速启动”功

能(采用原休眠文件 hiberfil.sys,关机时保存整个内核的虚拟地址空间)

关于虚拟内存的详细介绍:https://zhuanlan.zhihu.com/p/96098896

网络带宽承载业务量的测试:

首先明白带宽和宽带的区别:

带宽是量词,指的是网速的大小,比如1Mbps的意思是一 兆比特每秒,这个数值即是指带宽

宽带是名词,阐明网络的传输速率速很高。宽带的标准是不断发展变化的,如美国宽带网新标准:25Mbps下行/3Mbps上行 

首先在服务器上安装相应的流量监控软件(见https://jingyan.baidu.com/article/fcb5aff740cc16edaa4a71e4.html),然后在服务器上提供一个足够大小的文件,开启多个客户端(由于上行和下行是互不影响的,所以客户端和服务端可以在同一台电脑上,但最好不要这样,因为客户端和服务端在一起还可能受到其它因素的制约而导致测试结果不准确),服务器将文件分包发给每个客户端,当带宽占用达到最大时查看日志中每秒的业务量即可(这里仅仅是测上行)

其它:服务器性能测试截图


多进程:

进程基本操作见C语言技术网

ps命令:https://blog.csdn.net/lzufeng/article/details/83537275

grep命令:https://www.zsythink.net/archives/1733

常用:

ps -ef(用来查看进程,可以配合grep筛选和more分页查看)

ps -xH(用来查看线程,可以配合grep筛选和more分页查看)

SIGCHLD(子进程结束信号):该信号的默认值是忽略此信号,但这里的忽略和signal(SIGCHLD,SIG_IGN);不同,前者由于信号被忽略子进程结束之后保持不变(僵尸进程),而后者则在忽略的基础上将子进程交由系统进程托管,系统进程会释放该进程所占用的资源
注意SIG_IGN只有和SIGCHLD搭配使用时才会有上述效果,与其它信号搭配都是直接忽略掉该信号

fork:https://blog.csdn.net/gaolang663/article/details/27176149

fork的子进程会和父进程一样在前台运行,终端相同,且可以用Ctrl+C同时杀死两个进程,但一旦父进程结束,子进程由系统进程托管而变为后台进程(开启后台进程的另一种方式),无法通过Ctrl+C杀死

其它:多进程的服务器和客户端程序


信号:

进程信号基本操作见C语言技术网

进程信号阻塞需要用到sigprocmask(),该方法又用到了信号集,详见:https://www.bilibili.com/video/BV145411a76x?p=5

中断指突然停止执行了,阻塞指延迟执行

当一个信号到达进程后,调用处理函数,如果这时候有其它的信号发生,则会中断之前的处理函数,等新的信号处理函数执行完成后再继续执行之前的函数(不是重启)

但同一个信号会排队阻塞

对于系统调用被中断的情况,可以通过将sigaction方法的第二个参数的flag成员设置为SA_RESTART以重新开启被中断的系统调用,详见:https://www.bilibili.com/video/BV145411a76x?p=6

其它:多进程服务器和客户端程序


信号量:

可以结合文章和例子进行理解:

https://zhuanlan.zhihu.com/p/345000760

//消费者生产者问题
#include<iostream>
#include<semaphore.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>

#define produce_item(item) Item item; sleep(2); cout<<"produce finish"<<endl;
#define consume_item(item) sleep(1); cout<<"consume finish"<<endl;
#define n (unsigned int)5

using namespace std;

struct Item {};
int in = 0, out = 0;
Item buffer[n];
sem_t *mutex, *empty, *full;

void producer()
{
	do
	{
		produce_item(item);
		sem_wait(empty);
		sem_wait(mutex);
		buffer[in] = item;
		in = (in+1)%n;
		sem_post(mutex);
		sem_post(full);
	} while(true);
}

void consumer()
{
	do
	{
		sem_wait(full);
		sem_wait(mutex);
		Item item = buffer[out];
		out = (out+1)%n;
		sem_post(mutex);
		sem_post(empty);
		consume_item(item);
	} while(true);
}

int main()
{
	mutex = sem_open("/mutex",O_CREAT|O_RDWR,0666,1);
    // 信号量命名必须是以/开头,其并不是根目录的意思,该文件存于/dev/shm文件下
    // 标志位O_CREAT|O_RDWR有两个作用:该信号量不存在就创建,存在就打开
    // 0666为该信号量文件的权限
    // 1为信号量初始值(也可以设置为其他正整数)
	empty = sem_open("/empty",O_CREAT|O_RDWR,0666,n);
	full = sem_open("/full",O_CREAT|O_RDWR,0666,0);
	int pid = fork();
    if (pid!=0) 
		producer();
	else
		consumer();
    return 0;
}

多线程:

线程基本操作见C语言技术网

pthread_join()的第二个参数是一个用户定义的指针,用来存储被等待线程的返回值,注意与该函数的返回值区分,详见:https://baike.baidu.com/item/pthread_join/2296997?fr=aladdin

pthread_setcancelstate不调用时响应取消的方式默认为PTHREAD_CANCEL_ENABLE

pthread_setcanceltype不调用时线程的默认取消方式为PTHREAD_CANCEL_DEFERRED
线程取消相当于立即或在取消点调用了pthread_exit()方法,并没有完全释放线程占用的资源。所以当取消线程后,主函数中还应该用join来完全释放资源,或尽量减少线程取消的使用

从外部使用kill命令产生的信号,通常是SIGINT、SIGHUP等job control信号,会遍历所有线程,直到找到一个不阻塞该信号的线程,然后调用它来运行信号处理函数(一般从主线程找起),处理完之后再从上次中断的地方继续执行

如果使用pthread_sigmask函数将主线程信号阻塞,而子线程不阻塞,那将由子线程调用信号处理函数且会中断子线程

使用pthread_kill指定线程执行信号处理函数时,被指定的线程会中断以执行信号处理函数,执行完之后再从中断的地方继续上次的执行(本质上通外部调用一样,只不过一个是主线程一个是子线程)

其它:多线程截图,多线程服务器和客户端程序

Linux下进程和线程的区别:https://www.cnblogs.com/fah936861121/articles/8043187.html


锁:

互斥锁初始化方法的第二个参数见http://www.freecplus.net/78167da3101a4aeebb66918901ac089d.html,基本操作见互斥锁截图

读写锁初始化方法的第二个参数一般传NULL表示使用缺省值对锁进行初始化

关于timespec结构体可以表示高精度的时间,详见:https://www.cnblogs.com/hushaojun/p/7990951.html

关于pshared参数:
pshared参数指示此信号量是在进程的线程之间共享,还是在进程之间共享。如果pshared的值为0,则信号量在进程的线程之间共享,并且应位于所有线程都可见的某个地址。如果pshared的值为非零,则信号量在进程之间共享。并且应该位于共享内存的一个区域中

详见:http://ask.sov5.cn/q/VmVG43P0E2

其它:线程同步截图,fastcache程序(信号量和非信号量两种实现)


文件操作:

文件基本操作见C语言技术网

fopen模式总结:https://www.cnblogs.com/zhangzhi/archive/2011/01/07/1930158.html

fputc、putc都是向流(文件指针)里写字符
puts和fputs都是,前者向标准输出流写字符串,并附加\n,后者向流(文件指针)里写字符串,不加\n

fprintf用法同printf,只不过需要多加一个参数(放在最前面)表示向哪个文件指针中写,详见Reference

fgetc:从流里读字符

fgets:从流里读字符串,第一个参数表示用来保存读取内容的字符串,第二个参数表示读取字符数,第三个参数表示流,读取失败或到尾时返回0,读取成功返回读到的字符串

注意:如果读取到换行字符,则同样会接收该字符,但函数会跳出(无论size多大,只能读一行,而如果文件中将要读取的这一行内容长度大于size时,读size-1个字节)

详见Reference

文本文件和二进制文件区别:

https://www.cnblogs.com/zhangjiankun/archive/2011/11/27/2265184.html

fWrite:将指定内存区域中的值写入到流中,第一个参数为内存区域的起始地址,第二个参数为一个元素的字节数(常为1),即每次写多少字节,第三个参数为总共多少个元素(常sizeof(参数一)),第四个参数为待写入的流

原理:在内部,该函数将ptr指向的块解释为一个每个成员8位,共size*count个成员的数组,并将它们按顺序写入流(每次一个元素,若一个元素字节数为1,则相当于每次一个成员),就像为每个字节调用 fputc 一样

返回值:返回成功写入的元素总数

如果此数字与count参数不同,则写入错误会阻止函数完成。在这种情况下,将为流设置错误指示符(ferror)

如果size或count为零,则函数返回零且错误指示符保持不变

注意:如果count*size超过了sizeof(参数一),函数依旧可以正常执行,此时非法内存中数据将被写入文件(无大碍)

fRead:将指定流中的数据写入内存,参数原理同fWrite相同

返回值:返回成功读取的元素总数

如果此数字与count参数不同,则表示发生读取错误或读取时到达文件末尾。 在这两种情况下,都设置了适当的指标,可以分别使用ferror和feof进行检查

如果size或count为零,则该函数返回零,并且流状态和ptr指向的内容保持不变

注意:如果count*size大于sizeof(参数一),函数依旧正常执行,此时读取了非法内存中的内容

触发缓冲区刷新的操作:https://www.cnblogs.com/lysuns/p/4278944.html

文件定位,使用fseek可将文件位置指针移动到超过文件尾的地方,但不能前移到文件头之前,移动文件尾之后再写入,文件位置指针与文件尾之间会用空字符补全
ferror:检查是否设置了与流关联的错误指示符,如果是,则返回一个不同于零的值,否则返回0(读取到文件尾不算错误)
会被覆盖

用完及时clearerror,否则设置的值将一直保存

clearerror:清除流的eof和错误指示符

perror:将errno(http://www.cplusplus.com/reference/cerrno/errno/?kw=errno,与ferror作用类似,但性质完全不同,建议谷歌翻译)的值解释为错误消息,并将其打印到 stderr(标准错误输出流,通常是控制台),参数str(可选)表示错误消息之前的提示消息

/* writing errors */
//ferror、perror和clearerror
#include <stdio.h>
int main ()
{
  FILE * pFile;
  pFile = fopen("myfile.txt","r");
  if (pFile==NULL) perror ("Error opening file");
  else {
    fputc ('x',pFile);
    if (ferror (pFile)) {
      printf ("Error Writing to myfile.txt\n");
      clearerr (pFile);
    }
    fgetc (pFile);
    if (!ferror (pFile))
      printf ("No errors reading myfile.txt\n"); 
    fclose (pFile);
  }
  return 0;
}

feof:当某个流试图在文件结尾处或之后进行读取操作时会设置eof指示符。feof可以检查流是否被设置了eof指示符,若设置返回非0,否则返回0

会被覆盖

用完可以不用clearerror清理,下次IO操作前会自动清理

/* feof example: byte counter */
#include <stdio.h>
int main ()
{
  FILE * pFile;
  int n = 0;
  pFile = fopen ("myfile.txt","rb");
  if (pFile==NULL) perror ("Error opening file");
  else
  {
    while (fgetc(pFile) != EOF) { //fgetc:读取失败或者读取到尾时返回EOF,读取成功以int形式返回字符
      ++n;
    }
    if (feof(pFile)) {
      puts ("End-of-File reached.");
      printf ("Total number of bytes read: %d\n", n);
    }
    else puts ("End-of-File was not reached.");
    fclose (pFile);
  }
  return 0;
}

(Linux下的高级编程结束) 


其它:

C/C++内存分配:
1.栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2.堆区(heap):一般由程序员分配释放,若程序员不释放,程序结束时可能由OS(操作系统)回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3.全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后由系统释放。
4.文字常量区:常量字符串就是放在这里的。程序结束后由系统释放。
5.程序代码区:存放函数体的二进制代码

输出一个小数,默认情况下,会显示六位有效数字

\t表示水平制表符。如haha\t001,haha后面会有四个空格然后是001。如果ha\t001,则ha后面会有6个空格然后是001。在前面字符的数目不足8时,\t会在前面字符的后面补充空格,直到空格和字符的数目达到8为止;若前面字符数目等于8,则直接补8个空格;若前面字符数目大于8,则从第一个字符开始,每八个字符为一组,以最后一组的字符数作为是否补空格的依据

C++中局部变量不赋值就使用会报错

if后面加分号无论其中的条件是否满足if块中的语句都会执行C++中三目运算符返回变量,可以继续队变量赋值,C中不可以赋值

1e6大小的int数组大概4MB

数组名是常量,因此不可以赋值

C++中的枚举类型每个取值都有一个整数值与之对应,用cout输出只能输出整数值,也可以用一个介于两个取值对应整数之间的整数为一个枚举变量赋值(需要强转)

不要在类中声明大数组,很容易爆栈,可以将这样的大数组放在堆中,但要记得释放。申请堆中内存的方式:https://www.cnblogs.com/kangderui/archive/2010/01/04/1638555.html

C++中类中成员默认访问权限为private,而结构中成员默认为public,这也是C++中类和结构唯一的区别;而在C#中类和结构的区别则较多,且C#结构与C++结构有一个很明显的区别就是成员的默认访问权限不同,C#结构和类一样,成员默认都是私有的,而C++结构则默认公有

C++默认参数在声明和定义不在一起时只能写在声明处,定义处不用写默认参数

C++函数模板在声明和定义不在一起时声明和定义必须都加上template关键字

定位new在使用时可能会访问到未经申请的内存,虽然在这种情况下仍然能够正常运行,但不推荐

C++中的单参构造函数只是构造函数的一种,所以一般也要声明为公有,私有情况很少见

转换函数通运算符重载一样一般也要声明为公有方法,私有无法访问

引用和const在使用时需要注意的两点(从原理上理解):

非常量引用初始化时必须用左值,常量引用可以用非左值,但不能用一个常量引用来初始化一个非常量引用

C++中获取类的非静态成员函数的地址要用&而不能直接用函数名,C++的规定便是如此,详见:https://blog.csdn.net/qq_31629063/article/details/85331655

函数参数:在C++中,括号为空与在括号中使用关键字void是等效的——意味着函数没有参数。在ANSI C中,括号为空意味着不指出参数——这意味着将在后面定义参数列表。在C++中,不指定参数列表时应使用省略号:void function(...);通常,仅当与接受可变参数的C函数(如printf())交互时才需要这么做

va_list:用于表示参数列表的类型,需要用va_start初始化,详见:https://en.cppreference.com/w/cpp/utility/variadic/va_list

va_start:初始化va_list,第二个参数为调用va_start的方法中...之前的参数名(个人认为通过该参数的地址找到了其它的参数),详见:https://en.cppreference.com/w/cpp/utility/variadic/va_start

va_arg:用于逐个返回va_list中的参数,第二个参数表示参数类型,详见:https://en.cppreference.com/w/cpp/utility/variadic/va_arg

va_end:关闭va_list用,详见:https://en.cppreference.com/w/cpp/utility/variadic/va_end

snprintf,加了字符数量限制的sprintf,详见:http://www.cplusplus.com/reference/cstdio/snprintf/?kw=snprintf

vfprintf:与fprintf类似,但后面的参数列表可以直接写一个va_list类型的变量,详见:http://www.cplusplus.com/reference/cstdio/vfprintf/?kw=vfprintf

创建文件最好加后缀名,不加会产生不确定性

如果文件中有转义字符,将它们读入到文件中就是普通的字符,因为此时它们被视为两个字符而不是一个

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值