C++基础 第一遍学习

c基础学习

目录

c基础学习

1.编译器选择

2.安装vs2017

3.创建一个项目

4.printf函数/scanf函数

5.变量

5.常量

6.数组

7.表达式与操作符

8.语句

9.函数

10.指针

                                                                    C++

1.标准库

2.类型和声明

3.指针、数组和结构

4.动态内存分配

5.面向对象编程

6.c++高级

7.C++标准函数库

8.标准模板库

9.c++11速览


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) 控制台等待标准输入(即键盘输入)输入一个变量(此处是整数),再通过第二个参数 地址    找到内存地址处保存。

② 第一个参数是一个字符串,以双引号包围
③ 第二个参数,记得要在 a 前面加一个 &
注:键盘上敲入的字符会以ascii码(即字母以及控制字符等会转换成数字)存储在键盘缓冲区,scanf会等待用户敲击回车键(表示输入结束,注意此时回车键也作为字符存储在键盘缓冲区)

5.变量

其中short - 2

int - 4        long - 4    float - 4         double - 8   char - 1

无符号整数用 %u 作为控制符  
long long unsigned long long ,它们是 64 位的整数、占 8 字节的内存。

数的进制表示

十进制、十六进制、八进制、二进制

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.常量

1.含义:
        当在类型名前面加上关键字 const 后,表示它是一个只读的量。这种变量不能修改它的值,
因而称为常量。
const int  const char const float const double
2.作用:
可以定义常量
类型检查:这里相较于#define 宏定义常量的区别是,const常量具有类型,编译器可以进行检查,但宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查.
防止修改,起保护作用,增加程序健壮性
可以节省空间,避免不必要的内存分配:1)cons定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的事立即数
2)const定义的常量在程序运行过程中只用一份拷贝,而宏定义的常量在内存中有若干个拷贝
3.const对象默认为文件局部变量  若需要在其他文件中使用该常量,需要 在源文件中再加extern(注意常量需要初始化)
4.指针与const
与指针相关的const有四种:
const char * a    指向const对象的指针
char const * a     同上
char * const a 常量指针
const char * const a 指向const对象的const指针
const 在*左边,则const就是用来修饰指针所指向的变量,在*右边即修饰指针本身
注意: 1)必须使用const void * 类型的指针保存const 对象的地址
2)允许把非const对象的地址赋给const对象的指针 但是依然不能通过该指针修改这个对象的值
3)指针常量必须初始化,且不能初始化为常量的地址
4)对于类型来说,想要提高参数在函数中传递的效率可以使用引用传递参数,若想在函数体内不改变该参数的内容,可以加上const限定该引用不能更改.
5)使用关键字进行说明的成员函数,称为常成员函数,只用它能够操作常量或常对象
6)类中的const成员变量必须通过初始化列表进行初始化,const对象只能访问const成员函数

6.数组

1.一维数组
定义一个 short 数组
short a[4] = { 0x1111, 0x2222, 0x3333, 0x4444 };
由于一个 short 2 个字节,所以 4 short 就占了 8 个字节的内存。在内存中,
a[0],a[1],a[2],a[3] 可以视为连续排列的 8 short 型变量。下面的示意图阐述了这个概念。

2.二维数组

定义

int a[4][3] =

{
{ 11, 12, 13 },
{ 21, 22, 23 },
{ 31, 32, 33 },
{ 41, 42, 43 }
};
本质
二维数组,乃至三维、四维等高维数组,只是在形式上比一维数组更直观更容易操作,其
本质仍然是一维数组。

3.字符数组

字母: a b c ... z
数字: 0 1 2 ... 9
标点: + - * / ; , . 等等
控制字符: Tab, Enter 等等
把这几十字符分别用一个数字与之对应,表示这种对应关系的表称为 ASCII 码表。把一个
字符对应的那个数字,称为该字符的 ASCII 码。
需要注意的时,当用字符数组来存储字符串时,必须以 '\0' 结尾。我们把 '\0' 称为字符串的
结束符,它的 ASCII 码数值为 0
char str[6] = "hello";
下面是这个字符数组在内存中的情况
   
4.字符串输入输出
输出字符串
可以用 printf 来向控制台输出一个以 0 结尾的字符串,使用的格式符为 %s 。例如,
char str[6] = "hello";
printf("string: %s \n", str);
输入字符串
我们可以使用 gets 来获取一个字符串。在示例 CH05_C1 中,使用 gets 将用户输入的字符
串存入到数组 buf 中。注意,数组的长度要足够大,以免用户输入太长的字符串。
char buf[128]; // 定义一个足够大的数组
gets(buf);
gets()不读换行符以及EOF  文件尾
细节:0字符代表结束 空字符   代表一个字符串的结束   可以通过为字符数组中的某一元素赋值达到截断字符串的目的

7.表达式与操作符

1.算式操作符

对于整数来说:22/5 = 4    22%5 = 2

对于浮点型来说:22.0/5 = 4.4   但浮点型不能进行取模运算  比如 123.4%12.3  X

优先级从高到低依次为 *  /  %  +  -

2.赋值操作符

=   =号作左边是被赋值的   叫左值  一般为变量 数组元素

算术运算可以和赋值运行简写,于是增加了一些简写的操作符,如下表所示。

3.关系表达式

关系表达式是用于表示两个数的大小关系的式子。下表就是关系表达式的关系操作符

关系表达式的值要么是1要么是0(在 C++中以 零表示假,以非零表示为真

关系表达式是右值

4.条件表达式

条件表达式一般形式如下:

expr1 ? expr2 : expr3
意义:
expr1 为“真”时,条件表达式的值为 expr2
expr1 为“假”时,条件表达式的值为 expr3
5.逻辑表达式
&&:
expr1 && expr2
提前结算:当 expr1 为假时,结果已经明了,此时 expr2 不会被计算。
比如  3>4 && 1+2+4-8+21    从左边的关系表达式得出0那么与逻辑表达式的值就去确定下来了 后面的算术表达式就不会就计算了
||:
expr1 || expr2
判断规则:当 expr1 为真、或者 expr2 为真时,结果为真;否则为假。
同理:当expr1为真时,结果已经明了,此时expr2不会被计算
优先级 ! 高于 && 高于 ||
6.逗号表达式
逗号表达式是从逗式分开的一组表达式,计算的时候从左到右依次计算,最后一个表达式
的值为整个表达式的值。
ex1 , ex2 , ex3 , ..., exN
其中 , exN 是一个表达式。
7.自增/自减操作符
++ 是自增操作符,表示对变量加 1 -- 是自减操作符,表示对变量的值减 1 。只能作用于
整型变量,不能作用于浮点型变量。
例如:
int a = 10;
a ++; // 相当于 a=a+1
a --; // 相当于 a=a-1
++a; // 相当于 a=a+1
--a; // 相当于 a=a-1
前置时,先进行自增/自减操作,再执行所在行的表达式
后置时,先计算本行的表达式,之后再执行自增/自减操作。
8.位操作符
所有的位操作只适用于整数,即 char, short, int , unsigned char, unsigned short, unsigned int
更具体一点,只有无符号整数才适合使用位操作。
所谓按位即是操作符左右的数展开为二进制每一位进行操作
移位操作:
下面再看一下移位运算。再看一下 M(0xA7) 的表示:
M >> 1 表示 M 的所有位右移一位,左侧填充 0
M << 1 表示 M 的所有位左移一位,右侧填充 0
在工程一般不对有符号整数进行移位操作,因为对有符号数移位不直观,也没有什么实际意义。
负整数在右移时,高位(符号位)不变,左侧填充 1
其他情况下,均与无符号数的移位规则相同。

8.语句

1.重点看下switch语句
switch语句又称开关语句,它的作用是根据不同的选项,跳转到不同的分支处理。其语法形
式为:
switch(expr)
{
case OPTION_1:
break;
case OPTION_2:
break;
case OPTION_...:
break;
default:
break;
}
其中, expr :表达式的值必须为整型 OPTION: 必须为整形常量。
注:1)每个case需要加break表示直接跳出switch语句(与break相似的有contine,它常用在for while等循环语句中)
       2)default块的位置也可以不是固定在最后。
       3)case块可以嵌套,比如case 1:case 2:case 3:a = 6;break;(表明1 2 3 执行同样的语句)
2.for语句
使用 for 语句,可以完成一些可以用循环去完成的、带有规律性的工作。其语法形式为:
for ( expr1 ; expr2; expr3)
statement
其中, expr1,expr2,expr3 3 个表达式, statement 是一条单语句或复合语句。
语法规则:(循环如何被执行)
① 初始化:执行 expr1 。把 expr1 称为初始化表达式。
② 终止条件:执行 expr2 。若 expr2 的值为真,则执行第③步。如果 expr2 为假,则退出
for 语句。
③ 循环体:执行 statement
④ 后置表达式:执行 expr3
⑤ 继续下一轮循环:跳到第②步

9.函数

1.函数的调用

如下
01 #include <stdio.h>
02 int Cube (int a)   //求输入数的立方
03 {
04
int result = a * a * a; // 计算出结果
05 return result; // 返回结果
06 }
07 int main()
08 {
09 int n = Cube ( 4 ); // 调用函数:传入参数,得到返回值
10 printf(" result: %d \n", n);
11 return 0;
12 }
C/C++ 中,程序总是从 main 函数开始运行,于是程序是从第 [09] 行开始的:
=> 执行 [09] : 调用 Cube ( 4 )
=> 执行 [03] : 进入函数 Cube ,参变量被初始化 int a = 4
=> 执行 [04] : 计算 result 64
=> 执行 [05] : return 被执行,程序退出。
=> 执行 [09] : 回到原先函数调用的地方, Cube 函数返回值 64 被赋值给了 n
=> 执行 [10] : printf 打印出 result 的值
=> 执行 [11] : return 被执行, main 函数返回。当 main 函数返回时,意味着整个程序退出。
2.函数的传值调用
此处的a是参变量 与原变量n对应不同内存地址,在Test中修改a不会影响到n
void Test(int a)
{
a += 1000;
}
int main()
{
int n = 1;
Test(n);
printf("Now: n=%d \n", n); // n 的值是 1001 吗?
return 0;
}
实际上,函数的调用过程是传值调用。 Test(n) 所表示的意思是,“把 n 的值传给函数”,
而不是说把 n 这个变量传给函数。
3.全局变量和局部变量
定义在函数之外是全局变量, 它在各个函数中均可以访问;
局部变量的作用域(有效范围):
(1) 从定义之处起生效
(2) 至大括号结束后失效 ( 该变量所在的大括号 )
4.变量重名问题
1)不同函数内的变量,允许重名

2)可以和上一层次的变量重名

int a = 1;

if(1)
{
int a = 2; // 定义一个同名的变量
printf("level2: a=%d \n", a); // 访问的是本层级的变量 a ,打印值为 2
}
printf("end: a= %d\n", a); // 打印值为 1
return 0;
3)就近原则
#include <stdio.h>
int a = 100;
int main()
{
int a = 101;
if(1)
{
printf("a=%d \n", a); // 最近的变量 a 101
}
return 0;
}
5.函数声明
当一个 cpp 文件里有多个函数,且它们之间有调用关系时,那么它们的先后顺序就变得复杂。比如:main函数调用A函数,A函数调用B函数  A函数声明要在B函数声明之前
可以想象,当函 数越来越多时,要保证顺序就会变得麻烦。
函数的声明( Declaration ),在形式上就是把函数定义中的函数体去掉,只保留函数名、
参数列表、返回值类型。并以分号结束。例如,
int is_alpha(char ch);
void print_ascii();
这样,函数可以直接被调用,不用注意他们的顺序。只要把定义写在main函数之后就行。
注意:
1) 在函数声明中,参数名是可以省略的
2)函数声明中的参数名,和函数定义时的参数名可以不同
3)函数声明,作用是向编译器声明,存在这么一个函数,名字是什么,参数类型是什么,返回值是什么。这样编译器便心中有数,可以为我们检查函数调用的正确性。
6.参数的隐式转换
当调用函数时参数类型不匹配时,编译器尝试对参数进行隐式转换。如果允许隐式
转换,则编译通过。如果不能隐式转换,则编译不通过。
7.函数重载
C++ 里,允许两个函数的名字相同,称为函数名重载。也就是说,在 C++ 里只有名字相
同、参数列表也相同(参数个数,参数类型)也相同的函数,也被认定为重复。
例如,以下两个函数是不同的函数:
double find_max(double a, double b); // 求两个数中的较大值
double find_max(double a, double b, double c); // 求三个数中的最大值
当重载函数被调用时,编译器会根据参数的个数和类型来匹配不同的函数。

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);

buffer 是用来存储输入的地址(例如一个数组名)
length 是一个缓存 buffer 的最大容量
delimiter 是用来判断用户输入结束的字符(默认值是换行符'\n')

3)用cin<<string 时会有以下不如getline的地方

它只能接受单独的词(而不能是完整的句子),因为一些分隔符如空格、跳跃符、换行和回车都会中断

它不能给等待赋值的字符数组指定容量,如果用户输入超过数组长度,那么输入信息会被丢失。

4)字符串类型转换成其他类型

以下函数在cstdlib(stdlib.h)中被定义

atoi:将字符串string转换成int

atol:将字符串string转换成long

atof:将字符串string转换成float

例:int price = atof(mybuffer);

5)其他字符串处理函数(string.h)

 strcat: char* strcat (char* dest, const char* src); // 将字符串 src 附加到字符
dest 的末尾,返回 dest
strcmp: int strcmp (const char* string1, const char* string2); // 比较两个字符
string1 string2 。如果两个字符串相等,返回 0
strlen: size_t strlen (const char* string); // 返回字符串的长度

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)多维数组参数定义

例如 void procedure (int myarray[ ][3][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联合

union model_name {
type1 element1;
type2 element2;
type3 element3;
.
.
} object_name;
union 中的所有被声明的元素占据同一段内存空间,其大小取声明中最长的元素
的大小。
union 的用途之一是将一种较长的基本类型与由其它比较小的数据类型组成的结
(structure) 或数组 (array) 联合使用,例如:
union mix_t{
long l;
struct {
short hi;
short lo;
} s;
char c[4];
} mix;
9.staic
        staic关键字与不同类型一起使用:静态变量;静态类的成员(类对象和类中的函数)
 1)静态变量
        每次函数调用,函数中的静态变量的空间只分配一次。静态变量保存在静态数据区,它的生命周期看静态变量声明的位置。
        类中的静态变量也被存储在单独的静态空间,因此,类中的静态变量由对象共享。也是因为这个原因静态变量不能使用构造函数初始化。初始化需要单独地在类外初始化
        
#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变量时都是实在地从内存中读出。

        常见问题:
1)下面的函数可能会产生什么问题?
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);

char * ronny;
ronny = (char *) malloc (10);
int * bobby;
bobby = (int *) malloc (5 * sizeof(int));
2)calloc函数
void * calloc (size_t nelements, size_t size);
int * bobby;
bobby = (int *) calloc (5, sizeof(int));
其中calloc与malloc不同的是calloc会把所有的元素初始化为0。

3)realloc函数

它被用来改变已经被分配给一个指针的内存的长度。
void* realloc(void * pionter,size_t size);

realloc函数可能会重新改变内存块的地址以满足新的内存大小

若指向了新的内存地址,原来内存地址的内容会被复制

若无法分配新的内存地址,会返回一个空指针,原内存地址以及内容不会被改变

4)free函数

void free(void * pointer);

这个函数用来释放前面malloc、calloc、realloc所分配的内存块。

5.面向对象编程

1.类

类包含数据和函数,定义类的关键字为class

类的定义如下:

class class_name {
permission_label_1:
member1;
permission_label_2:
member2;
...
} object_name;
member1、member2这些是成员数据或者是函数
permission_label_1  _2这些是允许范围标志:
private;public;protected;
private:
只有同一个 class 的其他成员或该 class
friend class 可以访问这些成员
protected
只有同一个 class 的其他成员,或该 class
的“ friend class ,或该 class 的子类 (derived classes) 可以访问这些成员。
public 任何可以看到这个 class 的地方都可以访问这些
成员。
如果定义一个class成员时没有在某个允许范围下,默认为private范围。
在class中定义成员函数或变量必须使用范围操作符::
例如:
class CRectangle {
int x, y;
public:
void set_values (int,int);
int area (void) {return (x*y);}
};
void CRectangle::set_values (int a, int b) {
x = a;
y = b;
}
使用 class 的一个更大的好处是我们可以用它来定义多个不同对象(object)
  friend:
      友元提供了一种普通函数或者类成员函数访问另一个类中的私有或保护成员的机制。
优点:提高了程序的运行效率
缺点:破坏了类的封装性和数据的透明性
注意:友元关系不可传递且没有继承性;友元关系的单向性
友元函数
        在类声明的任何区域中声明,而定义则在类的外部。友元函数可以在任何地方调用。友元函数中通过对象名来访问该类的私有或保护成员。
友元类
        友元类的声明在该类的声明中,而实现在该类外。作为一个类的友元类可以直接访问A的私有成员。
     

2.构造函数和析构函数

1)构造函数

对象在生成过程中通常需要初始化变量或分配动态内存,为了避免变量未初始化被调用而发生错误,一个class可以包含一个特殊的函数:构造函数

它可以通过声明一个与 class 同名的函数来定义
当生成一个具体的对象时,这个构造函数自动被调用
#include <iostream.h>
class CRectangle {
int width, height;
public:
CRectangle (int,int);
int area (void) {return (width*height);}
};
CRectangle::CRectangle (int a, int b) {
width = a;
height = b;
}
...
CRectangle rect (3,4 );//这样可以直接传值给rect对象
CRectangle rectb (5,6); 
...
可以看到,
构造函数的原型和实现中都没有返回值(return value),也没有
void 类型声明。构造函数必须这样写。一个构造函数永远没有返回值,也不用声明
void

2)析构函数

析构函数 Destructor 完成相反的功能,它在 objects 被从内存中释放的时候被自
动调用。
析构函数必须与 class 同名,加水波号 tilde (~) 前缀,必须无返回值。
析构函数特别适用于当一个对象被动态分别内存空间,而在对象被销毁的时我们
希望释放它所占用的空间的时候。例如:
class CRectangle {
int *width, *height;
public:
CRectangle (int,int);
~CRectangle ();
int area (void) {return (*width * *height);}
};
CRectangle::CRectangle (int a, int b) {
width = new int;
height = new int;
*width = a;
*height = b;
}
CRectangle::~CRectangle () {
delete width;
delete height;
}
3)构造函数的重载和复制构造函数
像其它函数一样,一个构造函数也可以被多次重载 (overload) 为同样名字的函数,
但有不同的参数类型和个数。
复制构造函数: 使用复制构造函数可以使用现有的类对象生成新的类对象。
与构造函数不同的是,它的参数只能是一个引用类型的参数。
例如:
class EMployee
{
     public:
               EMployee(const Employee & e);
     ......
}
Employee emp1(xxx,xxxx);//调用构造函数
Employee emp2 = emp1;//直接调用复制构造函数
如果没有定义复制构造函数,编译器会自动添加一个构造函数;
在下面的情况会调用复制构造函数:
Ⅰ.定义类对象时直接用其他的类对象初始化,比如 Employee emp2 = emp1;
Ⅱ.调用函数参数时,参数是类类型,且没有定义为引用
Ⅲ.函数中返回类类型时,没有定义为引用
复制构造函数缺点: 在复制原类对象的指针成员时,复制构造函数只会复制指针,这两个指针指向同样的地址,这样显然是不可取的。

4)c++中的struct与union

类不仅可以用关键字 class 来定义,也可以用 struct union 来定义。
所以这两个关键字 struct class 的作用几乎是一样的(也就是说在 C++ struct 定义的类也可以有成员函数,而不仅
仅有数据成员 ) 两者定义的类的唯一区别在于由 class 定义的类所有成员的默认访问权限为 private,而 struct 定义的类所有成员默认访问权限为 public。
除此之外,两个关键字的作用是相同的。

union 的概念与 struct 和 class 定义的类不同, 因为 union 在同一时间只能存储一个数据成员。

但是由 union 定义的类也是可以有成员函数的。union 定义的类访问权限默认为 public

5)inline内联

内联是在编译阶段进行内联。成员函数在类中直接定义即为隐式内联函数。声明后,要想成为内联函数必须在定义处加,inline关键字。

编译器对inline函数的处理步骤:

1)将inline函数体复制到inline函数调用处;

2)为所用inline函数中的局部变量分配内存空间;

3)将inline函数的输入参数和返回值映射到调用方法的局部变量空间中;

4)如果inline函数有多个返回点,将其转变为inline函数代码块末尾的分支;

inline不适用的情况:内联是以代码膨胀为代价,仅仅省去了函数调用的开销,若执行函数体内代码的时间相比于函数调用的开销较大,那么效率收获会很低。比如说函数体内代码比较长或者出现循环。

3.类的指针

类也是可以有指针的,要定义类的指针,我们只需要认识到,类一旦被定义就成
为一种有效的数据类型,因此只需要用类的名字作为指针的名字就可以了。例如:
CRectangle * prect;

4.操作符重载

要想重载一个操作符,我们只需要编写一个成员函数,名为 operator ,后面跟我
们要重载的操作符,遵循以下原型定义:
type operator sign (parameters);
例如:
#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;
 }
函数 operator+ 的原型定义看起来很明显,因为它取操作符右边的对象为其
左边对象的函数 operator+ 的参数.

5.this

关键字 this 通常被用在一个 class 内部,指正在被执行的该 class 的对象 (object)
内存中的地址。
作用域:作用在类内部,在 类的非静态成员函数调用类的非静态成员函数的时候,this被编译器默认隐含参数被传递进函数中。

作用:

1它可以被用来检查传入一个对象的成员函数的参数是否是该对象本身

2)它还经常被用在成员函数 operator= 中,用来返回对象的指针 ( 避免使用临时对
)
3)当参数与成员变量名相同时,如this->n  = n;
对于this的类型有 (A是一个class)A * const或const A * ,当const使用this时,它的类型是后者。

6.静态成员 static

静态成员 可以是数据,也可以是函数
class 的声明中只能够包括静态成员 的原型 ( 声明 ) ,而不能够包括其定义 ( 初始化操作 )
初始化一个静态数据成员,我们必须在 class 之外 ( 在全域范围内 ) ,包括一个正式的
定义
例如:
class CDummy {
 public:
 static int n;
 CDummy () { n++; };
 ~CDummy () { n--; };
 };
 
 int CDummy::n=0;
一个 class 的静态数据成员也被称作类变量 "class variables"( 全域变量 ,因为它们的内容不依 赖于某个对象,对同一个 class 的所有 object 具有相同的值
作用:它可以被用作计算一个 class 声明的 objects 的个数。
static 函数是全域函数 (global functions) ,但是像一个指定 class的对象成员一样被调用。它们 只能够引用 static 数据 永远不能引用 class 的非静态
(nonstatic)成员。也不能使用this,因为this实际引用了一个对象指针。

7.类之间的关系

1)友元函数

为了实现允许一个外部函数访问 class private protected 成员,我们必须在 class 内部用关键字 friend 来声明该外部函数的原型,以指定允许该函数共享 class
成员。( 在函数最前面写friend 表明此函数是友元函数,可以被同类的其他对象所引用或者说此友元函数可以访问不同对象的private和protected成员
注意, 友元函数并没有当作class的成员,因此在声明时,不必加classname::
2)友元类
我们也可以定义一个 class 是另一个的 friend , 以便允许第二个 class 访问第一个 class protected private 成员。
class CSquare;
class CRectangle
{
    private:
    public:
    friend class CSquare;
}

此例中CSquare作为CRectangle的一个友元类,可以访问它的private和protected成员,但是CRectangle却不能访问CSquare的非public成员,换句话说友元关系不是相互的,除非我们将

CRectangle也定义为CSqure的友元类。

3)类之间的继承

类之间的继承是类的一个重要特征。它可以使一个类基于另一个类的某些成员而生成。

类的继承很大作用就是避免多个类中相同成员的多次定义。被继承的类叫做基类,继承的类叫做子类

要定义一个类的子类,我们必须在子类的声明中使用冒号 (colon) 操作符 : ,如下
所示:
class derived_class_name: permission labels base_class_name;
derived_class_name:子类
base_class_name:基类
permission labels:继承的允许范围,可以是public、private、protected
这里的permission labels是 指子类获得了基类的成员,这些成员应该得到private、public或者protected的保护;

protected和private在继承中的区别(这个区别是唯一的):当定义 一个子类的时候,基类的 protected 成员可以被子类的其它成员所使用,然而 private成员就不可以

下表按照谁能访问总结了不同的访问权限类型:

访问权限类型
能否访问publicprotectedprivate
本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); }
 };

一般来说子类继承基类的所有成员,除了:

构造函数和析构函数
operator=()成员
friends
多重继承:
一个 class 可以从多个 class 中继承属性或函数,只需要在子类的声明中用逗号将不同基类分开就可以了。
4)复合(composition)
class中有一个/些其它的结构component
这个class是一个容器(内存角度),也叫Adapter适配器
构造由内而外
这个class 的构造函数首先调用component的 默认构造函数(若想要调用特定的构造函数,需要在外部定义的构造函数中用冒号和有参数的形式写出),然后才执行自己
析构由外而内
class的析构函数首先执行,然后才调用component的析构函数
5)委托(composition by reference)
用指针相连

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)

Ⅱ.抽象基类不能有实例对象,但可以定义指向它的指针

这是因为该类包含的纯虚拟函数(pure virtual function) 是没有被实现的,而又不可能生成一个不包含它的所有成员定义的对象
Ⅲ.然而,因为这个函数在其子类中被完整的定义了,所以生成一个指向其子类的对象的指针是完全合法的
 

 抽象类和虚拟成员赋予了 C++ 多态(polymorphic)的特征。

看下面的程序,你会发现我们可以用同一种类型的指针 (CPolygon*) 指向不同类的对象,这一点非常有用。
#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 = &rect;
 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)一种数据类型函数模板   

例如:要生成一个模板,返回两个对象中较大的一个,我们可以这样写:

template <class GenericType>
GenericType GetMax (GenericType a, GenericType b) { return (a>b?a:b); }
实际使用时,
int x,y;
GetMax <int> (x,y); 此时int代替GenericType出现的地方
也可以写GetMax(x,y).此时编译器自动决定需要什么数据类型
2)多种数据类型函数模板
template <class T>
T GetMin (T a, U b) { return (a<b?a:b); }
实际使用时,
int i,j;
long l;
i = GetMin <int, long> (j,l);
也可以写成GetMin(j,l);
3)类模板
类模板可以使一个类可以基于通用类型的成员。

例如:

template <class T>
class pair {
T values [2];
public:
pair (T first, T second) {
values[0]=first;
values[1]=second;
}
};
这个类用来存储任意两个类型的元素。
使用时,
pair<int> myobject (115, 36);
pair<float> myfloats (3.0, 2.18); 可以看出这两条语句还能声明对象
上面的例子中,pair类唯一的成员函数被inline定义,在类之外定义如下:
template <class T>
class pair {
T value1, value2;
public:
pair (T first, T second) {
value1=first;
value2=second;
}
T getmax ();
};
}
T pair::getmax()
{
       .....
}
4)模板特殊化
模板特殊化是指当模板中的T有确定的类型时,会对应一个具体的不同的实现。
例如:
#include <iostream.h>
template <class T>     //类模板
class pair {
T value1, value2;
public:
pair (T first, T second){
value1=first;
value2=second;
}
T module () {return 0;}
};
template <>         //特殊化类模板
class pair <int> {
int value1, value2;
public:
pair (int first, int second){
value1=first;
value2=second;
}
int module ();
};
int pair<int>::module() {
return value1%value2;
}
从上面这个例子中,我们可以看到特殊化一个类模板需要重新定义类类模板中的所有成员,且T更换成这个特殊的数据类型。
5)模板的参数值
除了模板参数前面跟关键字 class typename 表示一个通用类型外,函数模板和类模板还可以包含其它不是代表一个类型的参数,例如代表一个常数,这些通常是基
本数据类型的。
template <class T> // 最常用的:一个 class 参数。
template <class T, class U> // 两个 class 参数。
template <class T, int N> // 一个 class 和一个整数。
template <class T = char> // 有一个默认值。
template <int Tfunc (int)> // 参数为一个函数。
例如:
template <class T, int N>
class array {
T memblock [N];
public:
void setmember (int x, T value);
};
template <class T, int N>
void array<T,N>::setmember (int x, T value) {
memblock[x]=value;
}
6)模板与多文件工程

2.名空间

名空间:一个全局范围

namespace identifier

{

    namespace-body

}

作用:防止有全局对象或者函数重名

例如:

namespace first {
int var = 5;
}
namespace second {
double var = 3.1416;
}
int main () {
cout << first::var << endl;  //使用时需要加上命名空间以及::范围操作符
cout << second::var << endl;
return 0;
}
使用:using namespace xxx;这条语句出现不同的位置有不同的效果
1)出现在main函数外,那么main函数中可以使用xxx中的内容(全局范围)
2)main函数中可以出现多个名空间,每个名空间作用范围可以用花括号表示,
例如:
int main () {

{

using namespace first;

cout << var << endl;

}

{

using namespace second;

cout << var << endl;

}

return 0;
}

别名定义:可以为已经存在的名空间定义别名,格式为:

namespace new_name = current_name ;
using:
        using可以改变派生类的访问性
        
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++新标准

1)当 throw 被执行的时候, try 语句块立即被停止执行,在 try 语句块中生成的所有对象会被销毁。此后,控制权被传递给相应的 catch 语句块(上面的例子中即执行仅有的一个 catch )。最后程序紧跟
catch 语句块继续向下执行,在上面的例子中就是执行 return 0;.
2)throw 语法与 return 相似,只是参数不需要扩在括号。
3)catch 语句块必须紧跟着 try 语句块后面,中间不能有其它的代码。 catch 捕获的
参数可以是任何有效的数据类型。 catch 甚至可以被重载以便能够接受不同类型的参数。
4)一个try语句中可以有多个throw,最后有相应个数的catch与其相对应
5)我们还可以定义一个 catch 语句块来捕获所有的例外,不论扔出的是什么类型的 参数。这种情况我们需要在 catch 或面的括号中写三个点来代替原来的参数类型和名
称,
try
{
      code here
}
catch(...)
{
      cout<<"Exception occurred";
}
6)try-catch也可嵌套使用
7)若没有例外throw catch没有捕获例外,特殊函数terminate将被调用。被调用时,会立即结束当前的进程的,并显示一个“非正常结束”的出错信息 。
8)没有throw的话,也有一些函数会扔出一些例外,如exception类下的一些参数
例如:
#include <iostream.h>
#include <exception>
#include <typeinfo>
try
{}
catch(std:: exception& e)
{
      cout<<"Exception"<<e.what();
}

4.类型转换高级

类的指针相互转换。ANSI-C++新标准定义了4种新的类型转换操作符。

1)reinterpret_cast <new type> (expression)  指针<->任意类型指针;指针<->整型(依据系统不同,对转换结果会有不同;若这个整型能容纳这个指针,那么这个整型可以再被转换回来)

2)dynamic_cast <new type> (expression) 完全被用来进行指针的操作。它可以用来进行任何可以隐含进行的转换操作以及它们被用于多态类情况下的方向操作。

可以对类进行上行或下行操作,不过基类中必须有虚函数

3)static_cast <new type > (expression) 它允许将一个引申类的指针转换为其基类类型(这是可以被隐含执行的有效转换),同时也允许进行相反的转换:将一个基类转换为一个 引申类类;

在后面一种情况中,不会检查被转换的基类是否真正完全是目标类型的。

4)除了能够对类指针进行操作,还可以被用来进行类中明确定义的转换,以及对基本类型的标准转换:

const_cast <new type > (expression)  这种类型转换对常量 const 进行设置或取消操作

5)tyeid操作符检查一个表达式的类型:

typeid (expression)

这个操作符返回一个类型为 type_info 的常量对象指针,这种类型定义在标准头函数中。这种返回值可以用操作符 == != 来互相进行比较,也可以用来通过 name()
函数获得一个描述数据类型或类名称的字符串
例如:
#include <iostream.h>
#include <typeinfo>
class CDummy { };
int main () {
CDummy* a,b;
if (typeid(a) != typeid(b)) {
cout << "a and b are of different types:\n";
cout << "a is: " << typeid(a).name() << '\n';
cout << "b is: " << typeid(b).name() << '\n';
}
return 0;
}

5.预处理指令 

1)#undef
#undef 完成与 #define 相反的工作,它取消对传入的参数的宏定义

2)#ifdef, #ifndef, #if, #endif, #else and #elif
这些指令可以使程序的一部分在某种条件下被忽略。
#ifdef 可以使一段程序只有在某个指定常量已经被定义了的情况下才被编译,无
论被定义的值是什么。
#ifdef name
// code here
#endif
#ifndef 起相反的作用:在指令 #ifndef #endif 之间的代码只有在某个常量没有
被定义的情况下才被编译

 一般会这样使用

#idndef XXXX

#define XXXX

XXXXXX

#endif             

指令 #if, #else #elif (elif = else if) 用来使得其后面所跟的程序部分只有在特定
条件下才被编译。这些条件只能够是常量表达式

3) #line   这里 number 是将会赋给下一行的新行数。它后面的行数从这一点逐个递增。

4)#error
这个指令将中断编译过程并返回一个参数中定义的出错信息,例如:
#ifndef __cplusplus
#error A C++ compiler is required
#endif

5)#pragma
这个指令是用来对编译器进行配置的

6)以下宏名称在任何时候都是定义好的:
macro
value
__LINE__ 整数值,表示当前正在编译的行在源文件中的行数。
__FILE__ 字符串,表示被编译的源文件的文件名。
__DATE__
一个格式为 "Mmm dd yyyy" 的字符串,存储编译开始的日期。
__TIME__ 一个格式为 "hh:mm:ss" 的字符串,存储编译开始的时间。
__cplusplus
整数值,所有 C++ 编译器都定义了这个常量为某个值。如果这
个编译器是完全遵守 C++ 标准的,它的值应该等于或大于 199711L ,具体值取决于它
遵守的是哪个版本的标准。
宏:
1)字符串化操作符(#)
在一个宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组。
#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.文件的输入输出   

1)C++ 通过以下几个类支持文件的输入输出:
ofstream: 写操作(输出)的文件类 ( ostream 引申而来 )
 ifstream: 读操作(输入)的文件类 ( istream 引申而来 )
 fstream: 可同时读写操作的文件类 ( iostream 引申而来 )

通过一个流对象打开一个文件,我们使用它的成员函数 open()
void open (const char * filename, openmode mode);
filename代表要打开的文件名
mode:ios::in 为输入(读)而打开文件
             ios::out 为输出(写)而打开文
  ios::ate 初始位置:文件尾

ios::trunc 如果文件已存在则先删除该文件

ios::binary 二进制方式

ios::app 所有输出附加在文件末尾

这些标识符可以组合使用,中间用 | 隔开
这三个类对于open函数的参数有着不同的默认方式
ofstream ios::out | ios::trunc
ifstream ios::in
fstream ios::in | ios::out
也可直接定义对象 例如 ifstream  myfile(const char * filename, openmode mode)
ofstream  myfile(const char * filename, openmode mode)

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.右值引用

//后续补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值