c基础学习
目录
1.编译器选择
首选vs系列 另外有c-Free 、Dev-C++、Eclipse C++、CodeBlocks
2.安装vs2017
这里安装了vs2017,安装教程粘过来:http://c.biancheng.net/view/456.html
3.创建一个项目
标点符号用半角英文
c++大小写敏感
半角全角区别 半角指的是一个字符占一个标准字符位置 全角指的是一个字符占两个标准字符位置
4.printf函数/scanf函数
1.printf(“%d%f%c/n/t”)
f指的是format
%4d 4表示显示4个字符宽度 #04d表示数据不足四位补零
%lf或者%f 表示单精度浮点型小数 %.2f 中.2可以规定小数点后几位 如果不写默认输出小数点后六位
double a = 12.35719987;
printf("x = %.2lf \n", a);
这里输出会是12.36;
double型 输入可以用%lf格式 输出%lf或者%f都可 输出时想要更高的精度(默认小数点后六位)得加上位宽 比如.2 .5 .10
%x 表示整数用十六进制表示
2.scanf("字符串",第二个参数)
scanf(“%d”,&a) 控制台等待标准输入(即键盘输入)输入一个变量(此处是整数),再通过第二个参数 地址 找到内存地址处保存。
5.变量
其中short - 2
int - 4 long - 4 float - 4 double - 8 char - 1
数的进制表示
十进制、十六进制、八进制、二进制
0或0x打头的数字为十六进制表示
其他进制都可以根据权重转化为10进制 比如 0111 1100 = 1 x 2^6 + 1 x 2^5 + 1 x 2^4 + 1 x 2^3 + 1 x 2^2 = 124
5.常量
6.数组
2.二维数组
定义
int a[4][3] =
3.字符数组
7.表达式与操作符
1.算式操作符
对于整数来说:22/5 = 4 22%5 = 2
对于浮点型来说:22.0/5 = 4.4 但浮点型不能进行取模运算 比如 123.4%12.3 X
优先级从高到低依次为 * / % + -
2.赋值操作符
= =号作左边是被赋值的 叫左值 一般为变量 数组元素
3.关系表达式
关系表达式的值要么是1要么是0(在 C++中以 零表示假,以非零表示为真)
关系表达式是右值
4.条件表达式
条件表达式一般形式如下:
8.语句
9.函数
1.函数的调用
2)可以和上一层次的变量重名
int a = 1;
10.指针
1.指针用于表示地址的一种新类型
char* short* int* float* double* unsigned char*
注:内存有地址0x0000000~0xFFFFFFFF,最小单元是字节
查看变量地址 printf(“%08x”,&a);或者%p打印地址
C++
1.标准库
2.类型和声明
1.#define和typedef的用法、区别、优缺点:https://kandianshare.html5.qq.com/v2/news/2849632946830747716?cardmode=1&docId=2849632946830747716&from_app=qb&sGuid=8871164c0dcb36505ffd78281bd888cb&sUserId=&sUserType=-1&sh_sid=5__156d0b780f2de595__8871164c0dcb36505ffd78281bd888cb&target_app=kb
2.string类(在string中被定义)
1)strcpy(string1,string2) //将string2的内容拷贝给string1
其中string2参数可以是数组、指针、字符串常量。
2)用cin给字符数组赋值
cin与getline一起使用
cin.getline(char buffer[],int length,char delimiter ='\n');
cin.getline(char buffer[],int length);
3)用cin<<string 时会有以下不如getline的地方
它只能接受单独的词(而不能是完整的句子),因为一些分隔符如空格、跳跃符、换行和回车都会中断
它不能给等待赋值的字符数组指定容量,如果用户输入超过数组长度,那么输入信息会被丢失。
4)字符串类型转换成其他类型
以下函数在cstdlib(stdlib.h)中被定义
atoi:将字符串string转换成int
atol:将字符串string转换成long
atof:将字符串string转换成float
例:int price = atof(mybuffer);
5)其他字符串处理函数(string.h)
3.指针、数组和结构
1.指针是一个类型,用此类型定义变量并赋值,那么此时这个变量即是地址
char c = 'o';
char* p = &c; //p指向字符变量c
下面是指针一些常见声明;
2.零
c++中0可以用作任意整型、浮点类型、指针、指针常量;
不同于C语言,c++中不用NULL代表指向空,而是直接使用0;
3.数组
1)
【】中必须是常量表达式。如果需要变化的界,需使用vector
2)字符数组
如果需要这种赋值,使用vector或valarray。
字符串定义中为使程序简洁,可以通过空白字符隔开
3)在函数中定义数组为参数
void prodedure(int arg[]);
调用时:int myarray[40];
procedure(myarray);
4)多维数组参数定义
4.常量
关键字const加到一个对象的声明上,将这个对象声明为一个常量,且必须初始化。例如
const最常见用途是作为数组的界和作为分情况标号。例如
5.指针和常量
这个在函数声明中特别好用,可以确保输入的的指针参数不会被改变
6.引用
记X&表示到X的引用。例如char& int&。
一个引用就是某对象的另一个名字。
int i = 1;
int& r = i;
现在r 和 i 都可以引用1这个值
int x = r; 此时x值为1
为了一个引用总是对应一个对象,我们必须对引用做初始化。
对于引用应用在函数参数时,应注意起函数名字让读程序的人知道该值传进去可能会改变值,或者可以让函数明确地返回一个值,或者要求一个指针参数
左值和右值的区别:C++术语辨析——左值和右值
7.指向void的指针
1)其他类型的指针可以赋值给void *的指针
2)两个void* 之间可以比较是否相等
3)void*指针可以显式转换到另一个类型。在使用void *之前 需要显式地将它转换成某个特定类型的指针
void*指针的重要用途:
Ⅰ.需要向函数传递一个指针,但是不能对这个参数对象类型有任何的假设
Ⅱ.从函数返回一个无类型的对象。
对象:是指具体的东西。类:是指抽象的东西 比如动物是一个类,复数是一个类,动物中的猫和狗就是一个具体的对象,复数中一个具体的复数也是一个对象,例如1+2i;
需要说明的是,类的定义中会有很多数据和函数成员,对象也会有同样的这些成员,其中static修饰符修饰的成员为所有对象所公用,即这个成员是唯一的,无论创建了多少个对象,这个成员只有一个
8.结构
结构体
1)结构体定义
注意此处的分号
2)结构体声明
访问结构体中成员可以用 点 访问
3)结构体初始化
注意jd.name不能用“NJ”的形式赋值,因为name是个长度为2的字符数组,“NJ”长度为3
4)指针间接访问结构体成员
5).结构体对象的大小不一定是其成员的大小之和,这是因为许多机器要求将确定类型的对象分配在某种与机器的系统结构有关的边界上。例如整数常常被分配到机器字的边界上。
对齐问题解释:https://blog.csdn.net/zyf983334665/article/details/106038784
6)类型的名字在出现之后就可以使用了,不必等到看到完整的声明
其次,
最后,结构体之间可以相互引用
7)struct是类的一个简单形式
8)类型等价
两个结构总是不同的类型,即使它们有着相同的成员
9.Union联合
#include <iostream>
using namespace std
class Apple
{
public:
static int i;
Apple()
{
};
};
int Apple::i = 1;
int mian()
{
Apple obj;
cout << obj.i;
}
2)静态成员
就像变量一样,对象被声明为static时具有范围,直到程序的生命周期。一般的对象生命周期视被声明所在的位置定。
就像类中的静态数据成员或静态变量一样,静态成员函数也不依赖类的对象。我们被允许使用对象和‘.’来调用静态成员函数。建议使用范围解析运算符调用。
10.volatile
作用:被volatile修饰的变量,在对其进行读写操作是,会引发一些可观测的副作用(这些副作用是由程序之外的因素决定的);
应用:
1)并行设备的硬件寄存器;
假设要对一个设备进行初始化,此设备的某一个寄存器为0xff800000
int *output = (unsigned int *)0xff800000; //定义一个IO端口;
int init(void)
{
int i;
for(i=0;i< 10;i++)
{
*output = i;
}
}
此时,编译器会选择优化这段for循环代码。它会认为,前面几次循环对最后的结果毫无影响,会直接将output这个指针赋值为9;但实际需要这么循环赋值,那么可以加上volatile,告诉编译器这个变量是一个不稳定的,遇到此变量不要优化。
2)一个中断服务子程序中访问到的变量
static int i=0;
int main()
{
while(1)
{
if(i) dosomething();
}
}
/* Interrupt service routine */
void IRS()
{
i=1;
}
上述代码意思是,在进入中断服务程序IRS并对i进行赋值后,在main函数的死循环中得以调用dosomething函数。实际上,编译器会判断出main函数中没有修改过i,因此可能只执行一次对i到某寄存器的操作,即自动初始化0,if判断的这个i,都只会使用某个寄存器中这个i的副本,导致dosomething永远不会被调用。此时,将变量i加上volatile修饰,则编译器保证对变量i的读写操作都不会被优化,从而保证了变量i被外部程序更改后能及时在源程序得到感知。
3)多线程中被多个任务共享的变量
此时应该用volatile声明。作用是防止编译器优化把变量从内存装入cpu寄存器中,当一个线程更改变量后,未及时同步到其他线程中导致程序出错。
volatile bool bStop=false; //bStop 为共享全局变量
//第一个线程
void threadFunc1()
{
...
while(!bStop){...}
}
//第二个线程终止上面的线程循环
void threadFunc2()
{
...
bStop = true;
}
上述程序是想通过第二个线程终止第一个线程的循环,如果bStop不用volatile声明,则第一个线程中读取的bStop是初始化后提取到寄存器的副本,值为false,而第二个线程对bStop的更新,不会被第一个线程所感知而停止循环,因为寄存器中bStop的值永远不会变成true。加入volatile的声明能够确保每次读bStop变量时都是实在地从内存中读出。
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
可能结果并不是对应整数的平方,因为在乘法操作中,ptr所指向的整数可能会被修改。
正确的写法如下:
long square(volatile int *ptr)
{
int a=*ptr;
return a * a;
}
2)。。。
11.assert
断言是宏,不是函数,定义在<<assert.h>>中。其作用是如果表达式返回错误,则终止程序执行
void assert(int expression);
#include <stdio.h>
#include <assert.h>
int main()
{
int x = 7;
/* Some big code in between and let's say x
is accidentally changed to 9 */
x = 9;
// Programmer assumes x to be 7 in rest of the code
assert(x==7);
/* Rest of the code */
return 0;
}
结果会返回
assert:assert.c:13:main:Assertion 'x==7' failed.
断言主要用于检查逻辑上不可能的情况。(断言通常在运行时被禁用,它主要检查代码运行前后的状态)
忽略断言:#define NDEBUG //在代码最开头写上
12.位域 bit field
位域是一种数据结构,可以把数据以位的形式紧凑储存,并允许程序员对此结构的位进行操作。
好处:1)节省存储空间(当面临成千上万个数据单元时)
2)位域可以很方便的访问一个整数值的部分内容从而简化程序源代码。
缺点:位域依赖于具体的机器和系统。它在不同平台上可能有不同的结果,具有不可移植性。
注意:
1)位域在内存中的布局是与机器相关的。
2)位域的类型必须是整型或者枚举类型,带符号类型中的位域行为将因具体实现而定。
3)取地址符&不能作用域位域,任何指针都无法指向类的位域。
使用:
struct bit_field_name
{
type member_name : width;
};
bit_field_name 位域结构名
type 位域成员类型,必须为int、signed int或者unsigned int
memeber_name 位域成员名
width 规定所占位数
c语言 使用unsigned int 作为位域的基本单位,即使一个结构的唯一成员为1bit的位域,该结构大小也和一个unsign int 大小相同(64位系统为32bits)
位域的对齐:
struct stuff
{
unsigned int field1: 30;
unsigned int field2: 4;
unsigned int field3: 3;
};
一个位域成员不允许跨越两个unsigned int 的边界,因此field2应该在空出2bits后存放;field3紧跟field2之后,该结构大小为2*32 = 64bits
我们也可以使用一个宽度为0的为命名位域成员令下一位域成员与下一整数对齐。
struct stuff
{
unsigned int field1: 30;
unsigned int : 2;
unsigned int field2: 4;
unsigned int : 0;
unsigned int field3: 3;
};
此时该结构大小为3*32 = 96bits
位域的初始化和位的重映射
初始化:
1)struct stuff s1 = {20,8,6};
2)struct stuff s1;
s1.field1 = 20;
s2.field2 = 8;
s3.field3 = 4;
重映射:
1) int *p = (int *) &s1;//将位域结构体的地址映射至整型int*的地址
*p = 0; //清除s1,将各成员清零
2)利用联合重映射,将位域归零
union u_box {
struct box st_box;
unsigned int ui_box;
};
union u_box u;
u.ui_box = 0; //位域归零
13.extern
通常我们在编写c++的程序时,会使用c语言编写的函数,在这个过程中需要注意,c++在编译函数的时候会加入表示参数类型的符号,而c不会,如 int add(int ,int) ,在c++编译后,会成为_add_int_int,而c中则为_add,这就导致编写cpp时使用c函数后链接发生错误,有两个办法能够解决:
1)在cpp中加入extern “C" add.h是add.c源程序中的头文件
#include <iostream>
using namespace std;
extern "C" {
#include "add.h"
}
int main() {
add(2,3);
return 0;
}
2)通常为了c代码能够通用,即既能够被c调用,又能被c++调用,add.h头文件通常会有如下写法
#ifdef __cplusplus
extern "C"{
#endif
int add(int x,int y);
#ifdef __cplusplus
}
#endif
这种方式使得C++者不需要额外的extern C,而标准库头文件通常也是类似的做法,否则你为何不需要extern C就可以直接使用stdio.h中的C函数呢?
那么反过来在c中调用c++的函数应该怎么办?在c中写extern “c”会导致编译错误,应该在c++头文件中写上extern “C"{int add();}
4.动态内存分配
1.new操作符用来动态分配内存
例如:int * boddy;
boddy = new int[5]; new表达式返回一个指针,这个指针指向5个int型字节的内存(如果没有内存可以分配,会返回一个NULL)
2.delete删除操作符用来删除指定内存
例如:delete[] boddy;
int *boddy = new int;
delete boddy;
3.c中对应的分配内存语句(stdlib.h)
1)malloc函数
void* malloc(size_t nbytes);
3)realloc函数
realloc函数可能会重新改变内存块的地址以满足新的内存大小
若指向了新的内存地址,原来内存地址的内容会被复制
若无法分配新的内存地址,会返回一个空指针,原内存地址以及内容不会被改变
4)free函数
void free(void * pointer);
这个函数用来释放前面malloc、calloc、realloc所分配的内存块。
5.面向对象编程
1.类
类包含数据和函数,定义类的关键字为class
类的定义如下:
2.构造函数和析构函数
1)构造函数
对象在生成过程中通常需要初始化变量或分配动态内存,为了避免变量未初始化被调用而发生错误,一个class可以包含一个特殊的函数:构造函数
2)析构函数
4)c++中的struct与union
union 的概念与 struct 和 class 定义的类不同, 因为 union 在同一时间只能存储一个数据成员。
但是由 union 定义的类也是可以有成员函数的。union 定义的类访问权限默认为 public。
5)inline内联
内联是在编译阶段进行内联。成员函数在类中直接定义即为隐式内联函数。声明后,要想成为内联函数必须在定义处加,inline关键字。
编译器对inline函数的处理步骤:
1)将inline函数体复制到inline函数调用处;
2)为所用inline函数中的局部变量分配内存空间;
3)将inline函数的输入参数和返回值映射到调用方法的局部变量空间中;
4)如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支;
inline不适用的情况:内联是以代码膨胀为代价,仅仅省去了函数调用的开销,若执行函数体内代码的时间相比于函数调用的开销较大,那么效率收获会很低。比如说函数体内代码比较长或者出现循环。
3.类的指针
4.操作符重载
例如:
#include <stdio.h>
class CVector{
public:
int x,y;
CVector(){};
CVector(int,int);
CVector operator + (CVector);
}
int main()
{
CVector a (3,1);
CVector b (1,2);
CVector c;
c = a + b;
cout << c.x << "," << c.y;
return 0;
}
CVector CVector::operator+(CVector param)//CVector是向量
{
CVector temp;
temp.x = x + param.x;
temp.y = y + param.y;
return(temp);
}
CVector::CVector (int a, int b) {
x = a;
y = b;
}
5.this
作用:
1)它可以被用来检查传入一个对象的成员函数的参数是否是该对象本身
6.静态成员 static
class CDummy {
public:
static int n;
CDummy () { n++; };
~CDummy () { n--; };
};
int CDummy::n=0;
7.类之间的关系
1)友元函数
class CSquare;
class CRectangle
{
private:
public:
friend class CSquare;
}
此例中CSquare作为CRectangle的一个友元类,可以访问它的private和protected成员,但是CRectangle却不能访问CSquare的非public成员,换句话说友元关系不是相互的,除非我们将
CRectangle也定义为CSqure的友元类。
3)类之间的继承
类之间的继承是类的一个重要特征。它可以使一个类基于另一个类的某些成员而生成。
类的继承很大作用就是避免多个类中相同成员的多次定义。被继承的类叫做基类,继承的类叫做子类
protected和private在继承中的区别(这个区别是唯一的):当定义 一个子类的时候,基类的 protected 成员可以被子类的其它成员所使用,然而 private成员就不可以
下表按照谁能访问总结了不同的访问权限类型:
能否访问 | public | protected | private |
---|---|---|---|
本class的成员 | 能 | 能 | 能 |
子类的成员 | 能 | 能 | 不能 |
非成员(其它class以外的地方) | 不能 | 不能 | 不能 |
继承例子:
Class CPolygon { //边长类
protected:
int width, height;
public:
void set_values (int a, int b) { width=a; height=b;}
};
class CRectangle: public CPolygon { //正方形面积
public:
int area (void){ return (width * height); }
};
class CTriangle: public CPolygon { //三角形面积
public:
int area (void){ return (width * height / 2); }
};
一般来说子类继承基类的所有成员,除了:
8.多态
1)基类和子类中的指针
类的指针:基类的指针可以指向子类的对象的地址,但这个指针只能引用从基类继承来的成员
若要想通过基类指针访问子类独有的成员需要在基类中声明需要访问的成员
2)虚拟成员
#include <iostream.h>
class CPolygon {
protected:
int width, height;
public:
void set_values (int a, int b) {
width=a;
height=b;
}
virtual int area (void) { return (0); }
//此处定义了虚拟成员,表明在通过基类指针访问子类该对应成员时,需细分
};
class CRectangle: public CPolygon {
public:
int area (void) { return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void) {
return (width * height / 2);
}
};
3)抽象基类
Ⅰ.包含纯虚拟函数的类被称为抽象基类(纯虚拟函数:比如virtual int area(void) = 0 简单地在函数声明后面写 = 0)
Ⅱ.抽象基类不能有实例对象,但可以定义指向它的指针
抽象类和虚拟成员赋予了 C++ 多态(polymorphic)的特征。
#include <iostream.h>
class CPolygon {
protected:
int width, height;
public:
140 / 177
void set_values (int a, int b) {
width=a;
height=b;
}
virtual int area (void) =0;
void printarea (void) {
cout << this->area() << endl;
//它可以将函数 area()的结果打印到屏幕上,而不必考虑具体是为哪一个子类。
}
};
class CRectangle: public CPolygon {
public:
int area (void) { return (width * height); }
};
class CTriangle: public CPolygon {
public:
int area (void) {
return (width * height / 2);
}
};
int main () {
CRectangle rect;
CTriangle trgl;
CPolygon * ppoly1 = ▭
CPolygon * ppoly2 = &trgl;
ppoly1->set_values (4,5);
ppoly2->set_values (4,5);
ppoly1->printarea();
ppoly2->printarea();
return 0;
}
9.复数例子
class complex
{
public:
complex (double r = 0, double i = 0) //不需要写返回类型//这里可以设置默认值
:re(r),im(i) //初始化 可以写多个构造函数即重载
{}
//构造函数complex() : re(0), im(0) {} 不能写成这样 因为前面的构造函数已经有默认值
//如果构造函数放在private区,这个类不能用 有singleton这种单例用法
complex& operator += (const complex&);//&是return by reference传引用加const 不能更改地址中的内容
double real () const {return re;}//不会改变数据内容的函数加上const
double imag () const {return im;}
private:
double re,im;
friend complex& __doap1 (complex*,const complex)
}
/*const complex c1(2,1) 2,1不能变
cout<<c1.real();
cout<<c1.imag(); //若上文real()imag()中未加const 这里的代码会出错
*/
/*
参数传递 pass by value vs pass by reference
complex& operator += (const complex&);
complex c1(2,1)
c2 += c1;这里传的是引用 跟指针类似
*/
/*
返回值传递 return by value vs return by reference
*/
/*
自由取得friend的private成员
*/
/*
相同class的各个objects互为friends
complex c1(2,1);
complex c2;
c2.func(c1);
*/
/*
*/
6.c++高级
1.模板
模板(Templates)是 ANSI-C++ 标准中新引入的概念。模板(Templates)使得我们可以生成通用的函数,这些函数能够接受任意数据类型的参数,可返回任意类型的值,而不需要对所有可能的数据类型进行函数重载。
1)一种数据类型函数模板
例如:要生成一个模板,返回两个对象中较大的一个,我们可以这样写:
例如:
2.名空间
名空间:一个全局范围
namespace identifier
{
namespace-body
}
作用:防止有全局对象或者函数重名
例如:
{
using namespace first;
cout << var << endl;
}
{
using namespace second;
cout << var << endl;
}
别名定义:可以为已经存在的名空间定义别名,格式为:
class Base{
public:
std::size_t size() const { return n; }
protected:
std::size_t n;
};
class Derived : private Base {
public:
using Base::size;
protected:
using Base::n;
};
在派生类Derived中,由于它是私有继承于Base,所以对于n和size函数来说,它们应该是私有的。using可以改变它们在Derived中的访问性。
函数重载
在继承过程中,派生类可以覆盖重载函数的0个或多个实例,一旦定义了一个重载版本,那么其他的重载版本都会变为不可见。
如果对于基类的重载函数,我们需要在派生类修改一个,又让其他的保持可见,必须要重载所有版本。
#include <iostream>
using namespace std;
class Base{
public:
void f(){ cout<<"f()"<<endl;
}
void f(int n){
cout<<"Base::f(int)"<<endl;
}
};
class Derived : private Base {
public:
using Base::f;
void f(int n){
cout<<"Derived::f(int)"<<endl;
}
};
int main()
{
Base b;
Derived d;
d.f();
d.f(1);
return 0;
}
取代typedef
对应于typedef A B ,可以使用using B = A;
3.出错处理
C++新标准
4.类型转换高级
类的指针相互转换。ANSI-C++新标准定义了4种新的类型转换操作符。
1)reinterpret_cast <new type> (expression) 指针<->任意类型指针;指针<->整型(依据系统不同,对转换结果会有不同;若这个整型能容纳这个指针,那么这个整型可以再被转换回来)
2)dynamic_cast <new type> (expression) 完全被用来进行指针的操作。它可以用来进行任何可以隐含进行的转换操作以及它们被用于多态类情况下的方向操作。
可以对类进行上行或下行操作,不过基类中必须有虚函数
3)static_cast <new type > (expression) 它允许将一个引申类的指针转换为其基类类型(这是可以被隐含执行的有效转换),同时也允许进行相反的转换:将一个基类转换为一个 引申类类;
在后面一种情况中,不会检查被转换的基类是否真正完全是目标类型的。
const_cast <new type > (expression) 这种类型转换对常量 const 进行设置或取消操作
5)tyeid操作符检查一个表达式的类型:
typeid (expression)
5.预处理指令
一般会这样使用
#idndef XXXX
#define XXXX
XXXXXX
#endif
3) #line 这里 number 是将会赋给下一行的新行数。它后面的行数从这一点逐个递增。
#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s
int main() {
exp("hello");
exp1(hello);
string str = exp2( bac );
cout<<str<<" "<<str.size()<<endl;
/**
* 忽略传入参数名前面和后面的空格。
*/
string str1 = exp2( asda bac );
/**
* 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
* 用每个子字符串之间以一个空格连接,忽略剩余空格。
*/
cout<<str1<<" "<<str1.size()<<endl;
return 0;
}
输出:
test s is:hello
test s is:hello
bac 3
asda bac 8
从结果可知,上述代码给出了基本的使用与空格处理规则,空格处理规则如下:
- 忽略传入参数名前面和后面的空格
- 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串之间以一个空格连接,忽略剩余空格。
2)符号链接操作符(##)
注意事项:
(1)当用##连接形参时,##前后的空格可有可无。
(2)连接后的实际参数名,必须为实际存在的参数名或是编译器已知的宏定义。
(3)如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开
#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s) //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_ ## s) //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
// 注意事项1
const char * gc_hello = "I am gc_hello";
expA(hello);
expB(hello1);
}
3)续行运算符 (\)
当定义的宏不能用一行表达完整时,可以用\表示下一行继续此宏的定义
#define MAX(a,b) ((a)>(b) ? (a) \
:(b))
int main() {
int max_val = MAX(3,6);
cout<<max_val<<endl;
}
do{...}while(0)的使用
1) 避免语义的曲解
#define fun() f1();f2()
if(a>0)
fun()
这个宏被展开就是:
if(a>0)
f1();
f2();
本意是同时执行两个函数,实际上条件判断成功仅会执行f1()且f2()每次都会执行。
可以在宏定义的时候加入{}块
#define fun() {f1();f2();}
if(a>0)
fun();
// 宏展开
if(a>0)
{
f1();
f2();
};
细心的人会注意,上述宏展开后多了一个分号,实际语法不太对(能运行)
2)避免使用goto控制流
有时候需要跳过一些代码,这个时候使用goto是十分简单的方法,但是由于goto不符合软件工程的结构化,而且有可能使得代码难懂,我们可以使用do{...}while(0)来做同样的事情
int f() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
cout<<*p<<endl;
#ifndef DEBUG
int error=1;
#endif
if(error)
goto END;
// dosomething
END:
cout<<"free"<<endl;
free(p);
return 0;
}
int ff() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
cout<<*p<<endl;
do{
#ifndef DEBUG
int error=1;
#endif
if(error)
break;
//dosomething
}while(0);
cout<<"free"<<endl;
free(p);
return 0;
}
3)避免由宏引起的警告
内核中由于不同架构的限制,很多时候会用到空宏,。在编译的时候,这些空宏会给出warning,为了避免这样的warning,我们可以使用do{...}while(0)来定义空宏:
#define EMPTYMICRO do{}while(0)
4)定义单一的函数块来完成复杂的操作
如果你有一个复杂的函数,变量很多,而且你不想要增加新的函数,可以使用do{...}while(0),将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。 这种情况应该是指一个变量多处使用(但每处的意义还不同),我们可以在每个do-while中缩小作用域,比如:
int fc()
{
int k1 = 10;
cout<<k1<<endl;
do{
int k1 = 100;
cout<<k1<<endl;
}while(0);
cout<<k1<<endl;
}
7.C++标准函数库
1.文件的输入输出
ios::trunc 如果文件已存在则先删除该文件
ios::binary 二进制方式
ios::app 所有输出附加在文件末尾
2)文件流中一些函数
- 关闭文件: void close();//在程序终止前关闭所有打开的文件
- 读写文件:
myfile是文本文件 myfile>>、myfile<<
二进制文件采用read(char *addressOfBuffer, int numberOfBytes)、write(char *addressOfBuffer, int numberOfBytes)) 必须使用 reinterpret_cast 将缓冲区的地址解释为指向 char 的指针。
- 设置文件位置指针:
seekg(long int ,mode) //istream
第一个参数是指定文件位置指针移动的字节数
第二个参数是用于指定指针的起始位置
ios::beg(默认的,从流的开头开始定位,编号为0)
ios::cur(从流的当前位置开始定位)
ios::end(从流的末尾开始定位)。
例如:myfile.seekg(n,ios::cur)//把文件的读指针从myfile文件当前位置向后移动n个字节
seekp(long int,mode)//ostream
类似,例如:myfile.seekp(n,ios:cur)//把文件的写指针从myfile文件当前位置向后移动n个字节
- 返回文件指针位置:
tellg()用于返回写入位置
tellp()用于返回读取位置
8.标准模板库
1.容器
1)序列式容器
Ⅰ.向量 vector 类似数组,他们最大的区别在于向量的内存空间可以根据需要增加,每当有元素加入且超过当前的内存空间,会开辟一块新的空间足够容纳全部元素,原来的内存空间会被删除
vector优点:查找比较快,删除、插入比较慢
构造vector:
其中 Type是模板参数。
例如:
vector<int> a;
vector<int> b(12);
vector(3,0);
vector<int> d(c);
增加元素:使用push_back
void push_back(const Type & _Val);
但是会存在频繁的内存空间变化,先调用reserve()方法可以解决。
一般使用reserve方法为向量保留两倍大小的空间长度
删除元素:
删除尾部元素:使用pop_back();
删除全部元素:使用clear();
更改元素:
向量可以通过[]来访问其中的元素,从而更改元素
交换所有元素:
swap(vector& other);
vector<int> a(3,1);
vector<int> b(2,3);
a.swap(b);
其他方法及函数:
insert插入方法:
erase删除方法:
end():容器的迭代器,容器的尾
begin():容器的迭代器,容器的头
Ⅱ.双向链表list
STL中使用的链表就是双向链表
构造list:
需要使用#include <list>
list<int> a;
list<int> b(12);
list<int> c(3,0);
list<int> d(c);
list<int> e(d.begin,d.end());
处理list的节点:
增删节点:push_front() pop_front()通用【 insert()push_back() pop_back() 】
删除节点:erase() remove(),它只要给出元素的值,链表会遍历找出该值对应的节点并删除
删除和排序:
删除重复元素:unique()方法,如果链表中有相邻元素相同,链表只会保留第一个
排序:sort() 排序方式是从小到大 也可以从大到小 但需要使用链表的反向迭代器reverse()方法
拼接和融合:
拼接:splice()
//_Where 是调用此方法list中需要插入的迭代器,_Right是需要插入的原链表
void splice(iterator _Where, list & _Right);
void splice(iterator _Where,list& _Right,iterator _Firtst);
void splice(iterator _Where,list<Allocator>& & _Right,iterator _First,iterator _Last);
//iterator _First,iterator _Laset 表示源链表中需要插入的开始位置和结束位置
splice方法调用后会把源链表删除且不会对新的链表排序。
融合:merge()
a.merge(b)
merge()一般是针对两个已经排好序的链表.在进行融合时,根据元素中大小关系,依次把原链表的每个元素插入到目标链表的合适位置处.最后会删除源链表
反向迭代器:
list<int>::reverse_iterator xxx = xxx;
rbegin()
rend()
反向迭代器与之前的迭代器是同理的,区别在于rbegin()指向链表的尾部,移动时向头部移动.rend()指向头部向尾部移动
Ⅲ.双端队列deque
如图所示,deque的中央控制器存放各段的首地址,在各段进行的操作互不影响,且每一段两端都可以增删元素。
#include <deque>
deque的处理
Ⅳ.stack
#include <stack>
模板类stack的声明:
template <
class value_type,
class Container=deque<value_type>
>
class stack;
push放入元素,pop弹出元素
每次只能通过top()方法访问栈顶的元素。
可以通过empty()方法验证容器是否包含元素
如果想要用list替代deque作为stack 的底层容器,可以写如下语句
Stack<int,list<int> > skInts;
2)关联式容器
关联式容器在元素插入后会立即进行排序。在存储结构上,关联式容器是以二叉树的形式表示的,树中每个节点分为键值和实值,根据键值进行排序。
关联式容器主要有集合和映射。
Ⅰ.二叉树
一棵树,限定书中每个父节点最多只能拥有两个子节点,这样的树叫做二叉树
高度:某个节点到其最深的子节点的路径长度
深度:根节点到某个节点的路径长度
二叉树的表示
template <typename T>
struct NODE
{
T m_data; //数据
NODE* m_pLeft; //左子树指针
NODE* m_pRight; //右子树指针
NODE(T value) : m_data(value), //节点构造函数
m_pLeft(0),m_pRight(0)
{
}
}
NODE nodeA('A');
NODE nodeB('B');
NODE nodeC('C');
nodeA->m_pLeft = &nodeB;
nodeA->m_pRight = &nodeC;
二叉树的遍历
广度优先遍历
按照节点深度由浅到深的次序进行遍历
深度优先遍历
优先遍历某棵子树,按照优先次序的不同分为:前序遍历、中序遍历、后序遍历
二叉搜索树
平衡二叉树
Ⅰ.映射map
<map>
map这个容器存储有对应关系的数据,即键值和实值
查找数据:[]有副作用、find()、lower_bound()、upper_bound() 若没有找到,返回一个迭代器,指向第一个键值比参数大的元素
遍历map:利用迭代器可以遍历、利用[]遍历(前提是map中键值有一定规律)
删除数据:clear()删除全部元素;erase()删除数据部分数据 参数有:键值引用、迭代器、两个迭代器表示区间
Ⅱ.set集合
9.c++11速览
1.新类型
新增类型long long和unsigned long long、char16_t和char32_t. 新增原始字符串
long long至少64位,且至少与long一样长
char16_t 无符号,16位 char32_t 无符号,32位
比如char16_t ch1 = u'q';
char32_t ch2 = U'q'; //u、U分别对应char16_t、char32_t
2.统一的初始化
1)扩大了大括号初始化列表的适用范围:
int x = {5};// = 可以不加
double y {2.75};
int * arr = new int[4] {2,4,6,7}
class a{...}:
a a1{5,43} //也可以用大括号调用构造函数
2)作用:初始化列表语句可防止缩窄,即禁止将数值赋给无法存储它的数值变量
char c2 = {453453534} //out of range
double c = {66} //int to double, allowed
3.声明
c++提供了简化声明的功能,尤其在使用模板时。
1)auto
auto用于实现自动类型推断,但这就要求显式初始化。
auto maton = 112;// maton is a type int
double fm(double,int)
auto pf = fm; //pf is a type double(*)(double,int)
另外auto还可以简化模板声明。
for(std::initializer_list<double>::iterator p = il.begin();p!= il.end();p++) {...} //其中il是一个std::initializer_list<double>对象
->for(auto p = il.begin();p!= il.end();p++) {...}
2)decltype
关键字decltype将变量的类型声明为表达式指定的类型。
decltype(x) y;
double x;
int n;
decltype(x*n) q;//q和x*n的类型一样为double
int j = 3;
int &k = j;
const int &n = j;
decltype(n) i1;//i1是const int &
decltype((j)) i2;//i2是int &
这在定义模板时特别有用
template<typename T,typename U>
void ef(T t,U u)
{
decltype(T*U) tu;
....
}
另外decltype与using/typedef合用,用于定义类型
using size_t = decltype(sizeof(0)); //sizeof(a)的返回值为size_t类型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
//...
}
重用匿名类型
struct
{
int d;
double b;
}anon_s;
这个结构体匿名,我们可以通过借助decltype重新使用这个匿名的结构体
decltype(anon_s) as; //定义了一个上面匿名的结构体
泛型编程中结合auto,用于追踪函数的返回值类型
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
return x*y;
}
3)返回类型后置
·double f1(double,int)
auto f2(double,int) ->double
//配合decltype来指定模板函数的返回类型
template <typename T,typename U>
auto eff(T t,U u) -> decltype(T*U)
{
...
}
4)模板别名
以前用typedef可以为复杂冗长的标识符创建别名
typedef std::vector<std::string>::iterator itType;
c++11提供新的创建别名的语法:
using itType = std::vector<std::string>::iterator;
两者差别在于,新语法可用于模板部分具体化:
template<typename T>
using arr12 = std::array<T,12>
则对于下述声明
std::array<double,12> a1;
std::array<std::string,12> a2;
可以替换为:
arr12<double> a1;
arr12<std::string> a2;
5)nullptr
空指针NULL 在c++中表示值为0,这会产生歧义,出于清晰和安全考虑,c++11使用nullptr
6)智能指针
第二遍学习有详细内容
4.异常规范方面的修改
void f501(int) throw(bad_dog); //can throw bad_dog exception
void f733(long long) throw(); //cannot throw an exception
->void f875(short,short) noexcept; //cannot throw an exception
5.作用域内枚举
enum有如下问题:
1)作用域不受限,会容易引起命名冲突。()
2)会隐式转换为int
3)用来表征枚举变量的实际类型不能明确指定,从而无法支持枚举类型的前向声明。
使用类、命名空间或者给枚举变量命名时加前缀可以解决上述第一个问题,但对于后面两个问题仍无能为力。
c++新增一种枚举
这种枚举可以用class或struct定义
enum old {yes,no,maybe} ; //传统形式
enum class New1 {never,sometimes,often,always}; //新形式
enum struct New2{never,lever,sever};
这样使用时,可以加上枚举名使用同一名字枚举成员 :New1::never New2::never;
1)新的enum的作用域不再是全局的
2)不能隐式转换成其他类型
/**
* @brief C++11的枚举类
* 下面等价于enum class Color2:int
*/
enum class Color2
{
RED=2,
YELLOW,
BLUE
};
r2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!
3)可以指定用特定的类型来存储enum
enum class Color3:char; // 前向声明
// 定义
enum class Color3:char
{
RED='r',
BLUE
};
char c3 = static_cast<char>(Color3::RED);
应用:类中的枚举类型
如何在整个类中建立恒定的常量?
我们知道类中的const数据成员,必须在构造函数初始化列表中初始化,这就导致了在创建不同对象的时候,该const数据成员可以不同。此时,不能借助const完成这个问题,可以在类中使用枚举类型。
class Person{
public:
typedef enum {
BOY = 0,
GIRL
}SexType;
};
//访问的时候通过,Person::BOY或者Person::GIRL来进行访问。
注意枚举常量不会占用对象的存储空间,他们在编译时被全部求值。
6.对类的修改
1)显示转换运算符
explicit 关键词
explicit 修饰构造函数时,可以防止隐式转换和复制初始化
explicit修饰转换函数时,可以防止隐式转换,但按语境转换除外。
2)类内成员初始化
可以在类定义中初始化成员,这样可避免在构造函数中编写重负的代码,从而降低出错率
7.模板和STL方面的修改
1) 基于范围的for循环
对于内置数组以及包含begin()和end()的类(如std::string)和STL容器,基于范围的for循环可简化为他们编写循环的工作。这种循环对数组或容器中的每个元素执行指定的操作
1.double prices[5] = {3.22,3.23,753.11,78.1,11.11};
2.for(double x:prices)
3.std::cout<<x<<std::endl; //x依次为price对应元素值
更安全的形式为,设置x为auto。如要改变数组中元素值,可使用引用类型:
2.for(auto & x:prices)
2) 新的STL容器
forward_list unordered_map unordered_multimap unordered_set unordered_multiset 第一种是一种单向链表,只能沿一个方向遍历,与双向链表容器相比,更简单且占用内存更少。后四种均用哈希表实现
新增模板arry
3)新的STL方法
cbegin()与cend()
4)valarray模板升级
5)摒弃export
export目的是提供一种途径,使得程序员能够将模板定义放在接口文件和实现文件中,其中前者包含原型和模板声明,而后者包含模板函数和方法的定义。实践证明这不现实。因此c++11终止了这一用法。
6)尖括号
为避免与>>混淆,c++要求在声明嵌套模板时使用空格将尖括号分开:
std::vector<std::list<int> > v1;
std::vecotr<std::list<int>> v1;
8.右值引用
//后续补充