目录
pass by value 与 pass by reference to const
输出指针的地址:cout << (void *)ptr << endl;
在循环中return和break的区别
return:直接跳出fun()函数,回到调用fun()的main()函数中 如下图
break:跳出当前循环,进入下一个循环的语句 如下图
i++与++i
i++表示先将i的值输出,再进行加一运算;
++i表示先对i进行加一运算,再将得到的值输出。
i++和++i的最重要的区别就是 +1和返回值顺序从效率上来说++i比i++来的更有效率,因为i++多声明了一个过渡的变量。
区别见下代码:
#include <stdio.h>
void print(int a, int b, int c)
{
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
}
void main(void)
{
int para1 = 5;
int para2 = 6;
int para3 = 7;
print(para1++, ++para2, para3);
print(para1, para2, para3);
}
//结果:
a = 5
b = 7
c = 7
a = 6
b = 7
c = 7
与或非
逻辑运算: 与 && 或 || 非 !
位运算: 与 & 或 | 非 !
各个数据类型的最大最小值
#include <limits.h>
#include <float.h>
#include "climits"
#include "iostream"
using namespace std;
//各个数据类型的最大最小值
int main(){
int n1= INT_MIN; //=-2^31=-2147483648
int n2= INT_MAX; //=2^31-1=2147483647
float f1 = FLT_MIN;
float f2 = FLT_MAX;
double d1 = DBL_MIN;
double d2 = DBL_MAX;
long ln1 = LONG_MAX;
long ln2 = LONG_MIN;
long long lln1 = LONG_LONG_MAX;
long long lln2 = LONG_LONG_MIN;
return 0;
}
递归
递归的先后顺序,即递归是利用栈来实现的,有后进先出的特性
类 构造函数 冒号 初始化列表
在刷力扣题108时,其中有类的初始化,直接蒙了,记录一下加强记忆,还是有很长的路要走呀🤣
贴上代码:
//一个定义二叉树结点的类
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
//以下三列都是构造函数
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right)
: val(x), left(left), right(right) {}
};
//创建类的对象时
TreeNode eaxmple_1;
TreeNode eaxmple_2(int num);
TreeNode eaxmple_3(int num, TreeNode *left, TreeNode *right);
c++中规定,const类型和引用不可以被赋值,只能被初始化;即const和引用在分配内存的时候就需要初始化,不可以在变量创建完成后再被赋值。在类中,对象的成员变量的初始化动作发生在进入构造函数本体之前。
如果类中含有这两种类型的数据,只能用构造函数后加冒号的方式来进行初始化,不能在构造函数内部进行初始化,因为在构造函数内部用a=1的语句初始化时,其过程为先创建数据a,再对a进行赋值。
而构造函数后面,冒号部分代码,是在进入构造函数并且在括号里面的第一行代码执行之前被执行。此时系统为成员变量分配了一块内存并且把相应的数据给填了进去。如下:
class A
{
public:
A(int& c): _b(2), _c(c){
_a = 1;
}
protected:
int _a;
const int _b;
int& _c;
}
类 对象的初始化 new 野指针
在那道力扣题中还有一句:
TreeNode* root = new TreeNode(nums[mid]);
- new创建的对象会赋值给类指针,指针用途广泛,比如作为函数返回值、函数参数等
- new创建的对象在使用完时,必须要使用delete销毁该对象,否则会造成内存泄露(new申请的对象,只有调用到delete时才会执行析构函数)
- 直接使用类名创建的对象,则不需要手动销毁该对象,因为该类的析构函数会自动执行。
- 执行new命令时,首先会开辟出一块内存,接着针对此内存会有一个或多个构造函数被调用;相应的,执行delete命令时,针对此内存会有一或多个析构函数被调用,接着内存被释放。
delete销毁new创建的指针对象的代码如下(记住):
if (NULL != root)
{
// 使用delete删除对象
delete root;
//delete [] root;若root是数组指针,则这样删除
root = NULL;
}
注意:此处不仅要delete指针对象,还要对其赋值为NULL,将该指针设置成空指针。
执行delete之后,root所指向的不再是原来的值,而是一个随机数,此时编译器只会释放该指针所指向的内存空间,而不会删除这个指针本身,如果后面程序再定义其他的指针* new,编译器默认将释放掉的内存空间回收然后分配给新开辟的空间,之前root指向的空间会被分配给new,但root也指向该空间,这会造成root和new指向同一空间,这就是野指针。
如果不将root设为空指针,将会出现野指针的情况。
for循环的第三个参数
当第三个参数不再是自增自减时,如下:
for(int j=i;j<n;j+=2*i)
string与int型数据之间的转换
int转string
string to_string(int val);//将int型数据转换成string型数据
string to_string (float val);//同上
string转int
string str1 = "45";
string str2 = "3.141592";
int myint1 = std::stoi(str1);// 输出45(string to int)
long myint2 = std::stol(str2);// 输出3(string to long)
char与int型数据的转换
char 转 int
char c='5';
int i=c-'0';
int 转 char
int i=10;
char c='0'+10;
多个string相加
//直接相加
string s1="xxx,",s2="yyy\n";
string s3=s1+s2; //s3的内容是xxxyyy\n
s1+=s2; //等价于s1=s1+s2
//使用append()函数
string s1= "xxx";
string s2="yyy";
s1.append(s2);
常量指针与指针常量
常量指针:常量的指针 const int * p 指针指向可变,指向的值不可变
指针常量:指针本身为常量 int * const p 指针指向不可变,指向的值可变
静态常量
const static 与static const的使用_weishan521520的专栏-CSDN博客
char string 比较
char是字符 string是字符串类型
char本质上是一个数组 string本质上是一个类
深拷贝(待补充)
在对类中的指针类数据进行默认拷贝时,会出现两个指针指向同一内存空间的状况
容器迭代器加减法的注意点
不是所有的容器的迭代器都支持类似于 begin()+i 的操作。
- 能进行算术运算的迭代器只有随机访问迭代器,要求容器元素存储在连续内存空间内,即vector、string、deque的迭代器是有加减法的;
- map、set、multimap、multiset、list的迭代器是没有加减法的。他们仅支持++itr、–itr这些操作。
关于指针的赋值运算符
指针的值实质是内存单元(即字节)的编号,所以指针单独从数值上看,也是整数,他们一般用16进制表示。
指针赋值和int变量赋值一样,就是将地址的值拷贝给另外一个,指针之间的赋值是一种浅拷贝。
int* p1 = &a;
int* p2 = p1;
p1和p2两指针本身所在的内存地址不同,但是它们俩所指向的内存地址相同,都是0028FF40。
pass by value 与 pass by reference to const
- 对于内置类型以及STL的迭代器和函数对象而言,pass by value比pass by reference to const更加高效
- 至于其他的数据,pass by reference to const更适合
关于char类型的一些思考
char和int都是用来存储0 1数据的内存,区别在于一个大小为一字节,另一个为四个字节,即存储的范围不一样。
char通常用来存储字符或字符串,用到' '与" "
单引号’ '的意思就是将字符的ASCII码存储在内存中,单引号起到的作用就是就该字符转化为ASCII码
双引号" "是将包含的字符串每一个都转化为相应的ASCII码存储在内存中
代码如下
char strng[] = "12345";
char x = 1;
char x1 = '1';
char x2[] = {1, 2, 3, 4, 5};
char x3[] = {'1', '2', '3', '4', '5'};
cout<<*strng<<endl
<<x<<endl
<<x1<<endl
<<*x2<<endl
<<*x3;
输出结果
strng=1
x=1
x1=49
x2=1
x3=49
关于类对象用成员函数访问其他对象private数据的问题
同一个类产生的不同对象之间互为友元,可以直接互相对private的数据进行操作
class A{
public:
A(int b) :a(b) {}//构造函数
void fun(const A & temp) {
a = temp.a;//猜想,因为在类内的成员函数中,所以可以通过对象
//访问private变量,如果在类外则不行
}
private:
int a;
};
在std内使用模板的注意点:
对于namespace std,用户可以全特化其内部的template,但是不能添加新的template
向上取整ceil() 向下取整floor()
If(a=b)的含义
可以分为两部分
a=b;
if(a!=0);
c++学习之new int()和new int[]的区别
new int[] 是创建一个int型数组,数组大小是在[]中指定,例如:
int * p = new int[3]; //申请一个动态整型数组,数组的长度为[]中的值
new int()是创建一个int型数,并且用()括号中的数据进行初始化,例如:
int *p = new int(10); // p指向一个值为10的int数。
typedef和define区别
typedef和define都是替一个对象取一个别名,以此增强程序的可读性,区别如下:
通常,使用 typedef 要比使用 #define 要好,特别是在有指针的场合里。
typedef double* tfd;//typedef用来定义类型的别名,起到类型易于记忆的功能
#define dfd double*;//#define 则是进行简单的进行字符串替换。
tfd a,b;//a,b两个都是double型的指针数据
dfd a1,b1;//相当于doule *a,b;a是double型的指针数据,b是double型的数据
cout << typeid(a1).name() << typeid(b1).name() << endl;//输出int *int
1.原理不同:
#define是C语言中定义的语法,是预处理指令,在预处理时进行简单而机械的字符串替换,不作正确性检查,只有在编译已被展开的源程序时才会发现可能的错误并报错。
typedef是关键字,在编译时处理,有类型检查功能。它在自己的作用域内给一个已经存在的类型一个别名,但不能在一个函数定义里面使用typedef。用typedef定义数组、指针、结构等类型会带来很大的方便,不仅使程序书写简单,也使意义明确,增强可读性。
2.作用域不同
#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用,而typedef有自己的作用域。
3.功能不同
typedef用来定义类型的别名,起到类型易于记忆的功能。#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
typedef与函数指针联合使用:
typedef void (*PFunCallBack)(char* pMsg, unsigned int nMsgLen);
上述声明引入了 PFunCallBack 类型作为函数指针的同义字,即将PFunCallBack作为某函数的别名,该函数有两个类型分别为 char* 和 unsigned int 参数,以及一个类型为 void 的返回值。通常,当某个函数的参数是一个回调函数时,可能会用到 typedef 简化声明。如下:
RedisSubCommand(const string& strKey, PFunCallBack pFunCallback, bool bOnlyOne);
RedisSubCommand 函数的参数是一个 PFunCallBack 类型的回调函数,返回某个函数(pFunCallback)的地址。在这个示例中,如果不用 typedef,RedisSubCommand函数声明如下:
RedisSubCommand(const string& strKey,
void (*pFunCallback) (char* pMsg, unsigned int nMsgLen), bool bOnlyOne);
int型数据与long型数据
int型数据与long型数据都占四个字节,即32位,因为早期的C编译器定义了long int占用4个字节,int占用2个字节。而long long 则是实打实的占8个字节 64位。
类型 | 存储字节 | 表示范围 |
---|---|---|
int | 4 | -2147483648~2147483647 |
short int | 2 | -32768~+32767 |
long | 4 | -2147483648~2147483647(二十亿,约1 0 10 10^{10}1010) |
long long | 8 | 9223372036854775808~+9223372036854775807(九百亿亿,约1 0 19 10^{19}1019) |
__int64 | 8 | 9223372036854775808~+9223372036854775807 |
补充:关于int型数据上下界的问题
int型数据在内存中占用4个字节,即用32位的比特位来表示,对于正数和负数,需要用到一个符号位来区别,正数的符号位是0,负数的符号位是1。那么如果是4字节的数据,从0x0000~0x7fff表示的是0以及正数1 ~ -1(0111 1111 1111 1111 =
-1) 从0x8000~0xffff用来表示负数,在此范围内,一共有
个数,(因为1000 0000 0000 0000也内用来表示负数)
空指针与野指针
值为0 (NULL)的指针叫 空指针,
空指针保证与任何对象或函数的指针值都不相等。反过来说,任何对象或者函数的地址都不可能是空指针,每种指针类型都有一个空指针,而不同类型的空指针的内部表示可能不尽相同。
空指针是一个特殊的指针,因为这个指针不指向任何地方。这意味任何一个有效的指针如果和空指针进行相等的比较运算时,结果都是false。
在程序中,得到一个空指针最直接的方法就是运用预定义的NULL,这个值在多个头文件中都有定义。
如果要初始化一个空指针,可以这样,
int *ip = NULL;
校验一个指针是否为一个有效指针时,更倾向于使用这种方式
if(ip != NULL)
而不是
if(ip)
但C/C++中定义的NULL即为0,而C++中的true为≠0,所以此时的if(ip)和if(ip != NULL)是等效的。
野指针不是空指针,是一个指向垃圾内存的指针
形成原因
1.指针变量没有被初始化。
任何指针变量被刚创建时不会被自动初始化为NULL指针,它的缺省值是随机的。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存,若初始化时不对其进行赋值,则为野指针,但若后期令其指向合法地址,则野指针变为正常指针。例如:
char* p = NULL;
char* str = (char*)malloc(1024);
int *ptr;//定义的时候没有初始化,此时ptr为野指针,很有可能指向的是未申请的内存等等
int a=10;
ptr=&a;//将ptr指向a,此时指针指向是确定的也是合法的
cout<<(*ptr)<<endl;//当对ptr进行解引用时,此时就要看看ptr的指向是否已确定
//由于上面我们将p指向了a变量所在内存位置,所以上面的操作是正确的
2.指针被free或者delete之后,没有设置为NULL,让人误以为这是一个合法指针。
free和delete只是把指针所指向的内存给释放掉,但并没有把指针本身给清理掉。这时候的指针依然指向原来的位置,只不过这个位置的内存数据已经被毁尸灭迹,此时的这个指针指向的内存就是一个垃圾内存。但是此时的指针由于并不是一个NULL指针(在没有置为NULL的前提下),在做如下指针校验的时候
if(p != NULL)
会逃过校验,此时的p不是一个NULL指针,也不指向一个合法的内存块,造成会面程序中指针访问的失败。
char *p = (char *) malloc(100);
strcpy(p, “hello”);
free(p); // p 所指的内存被释放,但是p所指的地址仍然不变
…
if(p != NULL) // 没有起到防错作用
{
strcpy(p, “world”); // 出错
}
3.指针操作超越了变量的作用范围。
由于C/C++中指针有++操作,因而在执行该操作的时候,稍有不慎,就容易指针访问越界,访问了一个不该访问的内存,结果程序崩溃
另一种情况是指针指向一个临时变量的引用,当该变量被释放时,此时的指针就变成了一个野指针,如下,函数Test在执行语句p->Func()时,对象a已经消失,而p是指向a的,所以p就成了“野指针”。
class A
{
public:
void Func(void){ cout << “Func of class A” << endl; }
};
void Test(void)
{
A *p;
{
A a;
p = &a; // 注意 a 的生命期 ,只在这个程序块中(花括号里面的两行),而不是整个test函数
}
p->Func(); // p是“野指针”
}
realloc与野指针
relloc函数用来为指针重新分配大小为size的一块内存:
void * realloc ( void * ptr, size_t new_size );
函数工作过程如下:
查看new_size大小:
- 如果new_size大小为0,则相当于free(ptr),将ptr指针释放,返回NULL,
- 如果new_size小于原大小,则ptr中的数据可能会丢失,只有new_size大小的数据会保存,
- 如果size等于原大小,等于啥都没做,
- 如果size大于原大小,则查看ptr所在的位置还有没有足够的连续内存空间,如果有的话,分配更多的空间,返回的地址和ptr相同,如果没有的话,在更大的空间内查找,如果找到size大小的空间,将旧的内容拷贝到新的内存中,把旧的内存释放掉,则返回新地址,(此时原来ptr指向的内存会被释放回到堆上,ptr成为野指针,它指向了一块没有分配给用户使用的内存,再访问的时候会发生错误,应使用free()函数将内存块释放)否则返回NULL。
补充:malloc,realloc这种用户自己开辟的内存都被分配在堆区,需要用户手动进行释放。
单独大括号的意义
const 迭代器与const_iterator区别
const迭代器,顾名思义,就是不能改变的迭代器,是指针常量
如 const vector<int> :: iterator iter = vv.begin();
iter是一个常量,因此是不能改变的
++iter; // error
(*iter)++;//right
虽然iter不能指向其他的元素,但是其指向的第一个元素的值是可以改变
vector<int> :: const_iterator iter;
这个迭代器是可以自己增加的,但是其所指向的元素是不可以被改变的,即常量指针
for(iter = vv.begin(); iter != vv.end(); ++iter){
cout << *iter << endl;
}
for(iter = vv.begin(); iter != vv.end(); ++ iter){
*iter = 0; //error
}
类内的静态成员变量和静态成员函数
C++中类的(static)静态成员变量与(static)静态成员函数_年少轻狂,幸福时光-CSDN博客_c++静态成员变量
- 静态成员变量属于整个类所有
- 静态成员变量的生命期不依赖于任何对象,为程序的生命周期
- 可以通过类名直接访问公有静态成员变量
- 所有对象共享类的静态成员变量
- 可以通过对象名访问公有静态成员变量
- 静态成员变量需要在类外单独分配空间
- 静态成员变量在程序内部位于全局数据区 (Type className::VarName = value)
- 静态成员函数是类的一个特殊的成员函数
- 静态成员函数属于整个类所有,没有this指针
- 静态成员函数只能直接访问静态成员变量和静态成员函数
- 可以通过类名直接访问类的公有静态成员函数
- 可以通过对象名访问类的公有静态成员函数
- 定义静态成员函数,直接使用static关键字修饰即可
for循环的三个表达式执行的顺序
int i;
for(;i<10;i++){
cout<<i<<endl;
}
程序结束时,i的值为10,for循环的执行顺序为,以第一个表达式为入口,进入循环;判断第二个表达式,若符合,则执行循环体,接着退出循环体,执行第三个表达式,若i值任然满足第二个表达式,继续执行循环体。按第二个表达式——循环体——第三个表达式——第二个表达式——。。。循环往复
const加引用
引用本质上是一个指针常量,而const加引用,一般用于函数的形参声明如
func(const int & num){...}
它保证了传入函数的实参在函数体内不会被改变,并且是实参传入函数时不会额外调用拷贝函数,节省空间,提高效率。
#if #else ...的作用
关于#
- #运算符用于在预处理期将宏参数转换为字符串
- 在预处理期完成,因此只在宏定义中有效
- 编译器不知道#的转换作用
条件编译是C语言中预处理部分的内容,它是编译器编译代码时,最先处理的部分。
#if 、#else 、#elif 及 #endif属于条件编译里的判断语句,它的意思是如果宏条件符合,编译器就编译这段代码,否则,编译器就忽略这段代码而不编译:
#define A 0 //把A定义为0
#if (A > 1)
printf( "A > 1"); //编译器没有编译该语句,该语句不生成汇编代码
#elif (A == 1)
printf( "A == 1"); //编译器没有编译该语句,该语句不生成汇编代码
#else
printf( "A < 1"); //编译器编译了这段代码,且生成了汇编代码,执行该语句
#endif
#if的后面接的是表达式:
#if ( MAX == 10 ) || ( MAX == 20 )
code ...
#endif
它的作用是:如果(MAX==10)||(MAX==20)成立,那么编译器就会把其中的#if 与 #endif之间的代码编译进去(注意:是编译进去,不是执行!!)
#if defined后面接的是一个宏:
#if defined (x)
...code...
#endif
这个#if defined,它不管里面的“x”的逻辑是“真”还是“假”,它只关注该程序的前面的宏定义里面有没有定义“x”这个宏,如果定义了x这个宏,那么,编译器会编译中间code部分,否则直接忽视中间的code代码。另外 #if defined(x)也可以取反,也就用 #if !defined(x)
#ifdef的使用:#ifdef的使用和#if defined()的用法一致 ;#ifndef又和#if !defined()的用法一致。
而 if 语句则不然,if 是 C 语言中的关键字,它根据表达式的计算结果来觉定执行那个语句,它里面的每个分支都编译了的, 如
#define A 0
if (A > 1)
printf( "A > 1"); //编译器编译该语句,但因为A == 0 未执行
else if(A == 1)
printf( "A == 1"); //编译器编译该语句,但因为A == 0 未执行
else
printf( "A < 1"); //编译器编译该语句,因为A == 0 故执行
作为一个编译“开关”(常用来注释代码),比如:
#if(条件满足)
执行代码 1
#else
执行代码 2
#endif
假如编译时,确实满足条件(结果非0时),则生成的程序文件(.exe文件)中不会有执行代码2的部分。如果用普通if语句,生成的程序文件就会有执行代码2的部分,这个区别从生成文件的大小可以分辨。如果条件在程序编译前就已经确定了,那就用#if;如果条件需要在程序运行过程中才能判断,则用if。
综上,条件编译是根据宏条件选择性地编译语句,它是编译器在编译代码时完成的;条件语句是根据条件表达式选择性地执行语句,它是在程序运行时进行的。
指针占用内存大小:
一个指针在32位的计算机上,占4个字节;
一个指针在64位的计算机上,占8个字节。
小李飞刀:位运算
位运算是针对于二进制的运算,因为计算机的运算模式是以二进制为基础,所以十进制运算在计算时会被转换成二进制再进行运算,而转换过程就会导致运行速度降低。所以运用位运算可以提高代码运行的效率。
知识储备:十进制的二进制表示
对于int型数据来说,占四个字节,32位。十进制的二进制表示有三种形式:原码,反码,补码
对于正数,其原码,反码,补码完全相同。
对于负数,如下:
10000000 00000000 00000000 00000111 // 是-7的原码 最右端加上符号位 11111111 11111111 11111111 11111000 // 反码:对-7的原码取反(除符号位) 11111111 11111111 11111111 11111001 // 最后一位加1,即得到-7的补码, //为计算机中最终的表示形式
基本的位运算有:按位与(&) 按位或(|) 按位异或(^)【相异为真】 按位取反(~)
进阶的位运算为: 左移(<<) 将这个数中的所有位向左移,空出的位补上0
右移(>>) 将这个数中的所有位向左移,空出的位补上符号位(正数为0,负数为1)
要注意关于移位运算 x>>2是非法的, 应该写成 x=x>>2;
5<<2=20 :0000 0000 0000 0101 ----> 0000 0000 0001 0100
-5>>2=-2 :1111 1111 1111 1011 ----> 1111 1111 1111 1110 ---->
1111 1111 1111 1101 ----> 1000 0000 0000 0010
位的与运算与或运算
如果先有一个八位二进制数据,想要将它的低六位提取出来,则可以用位的与运算,
int a=10111101B;
a=a | 00111111B;
a=a<<2;
与运算中是1位在起作用,如一个数与 0011 11111B 相与,得到的效果是低六位保留原来的结果,高两位直接置为0。
如果有一个八位二进制数据可表示为 011x xxxxB,即高两位固定,低六位可变,现要用上述a的低六位来填充。可以用位的或运算
//a 此时为 11 1101B
res=01100000B | a;
或运算中是0位在起作用,如一个数与 res = 0110 0000B 相与,得到的效果将用低五位代替res的低五位,res的第六位,第七位不变,热水的第八位也要改为a的第八位,但此处a只有六位,可以不考虑。
位运算的高效用法
1<<n; // 2的n次方
n<<m; // 计算n*(2^m)
n>>m; // 计算n/(2^m)
//判断一个数是否为奇数
if(n&1){
//n是奇数
}
else{
//n是偶数
}
do while(0)的应用
do{...}while(0)的意义和用法 -- 编程语言 -- IT技术博客大学习 -- 共学习 共进步!
可以总结如下:
1.如果宏里有多过一个语句(statement),就需要用 do { /*...*/ } while(0) 包裹成单个语句
2.用break和do{...}while语句来代替goto语句,实现函数的跳转。
unsigned_int 的最大值
unsigned_int占四个字节,32位,则最大为11111111 11111111 11111111 11111111,并且-1的补码为11111111 11111111 11111111 11111111,计算机都是对补码进行运算,所以最大值为-1。
enum关键字
enum(枚举)类型的作用概括起来就是:将多条#define 语句合并起来
如下语句,看起来十分繁琐
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
可以用枚举来简化
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} weekday; //DAY为枚举类型名;weekday是一个具体的枚举对象
weekday = WED;
cout<<weekday<<endl;// 输出 3
weekday一次可以取一个枚举值
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。此处实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
enum season {spring, summer=3, autumn, winter};
//依次为 0 3 4 5
断言(assert() )的应用
assert(表达式),表达式为真时,不会影响程序的运行,若表达式为false,则会退出程序并报错
#if 0 ... #endif
块注释符(/*...*/)是不可以嵌套使用的。
我们可以使用 #if 0 ... #endif 来实现注释,且可以实现嵌套,格式为
#if 0
code
#endif
将#if 0改为#if 1 即可调用代码段
union与struct
union是一种“类似”于struct的联合体,其中所有成员引用的是内存中的相同位置,以最大的成员的内存长度作为union的内存大小。union主要用来节省空间,默认的访问权限是公有的。
代码一:
typedef struct {
union {
struct { char* s; size_t len; }s; /* string */
double n; /* number */
}u;
lept_type type;
}lept_value;
代码二:
typedef struct {
char* s;
size_t len;
double n;
lept_type type;
}lept_value;
如上,在32位平台上,代码一的内存布局为黄色图,代码二的布局为绿色图。
union与stuct的不同
- 对于union 同一个内存段可以用来存放几种不同类型的成员,但在每一个时刻只能存在其中一种,而不能同时存放几种,即每一瞬间只有一个成员起作用,其它的成员不起作用,不能同时都存在和起作用;
- 对union的不同成员赋值,将会对其他成员重写,原来成员的值就不存在了,
指针的算数运算
*p++等同于*(p)++ ,先解析指针的值,接着将指针后移一个单位
char cp[] = "abcdefg";
char * p=cp;
cout<<(void *)p<<endl; //0x7ffd011fe2e0
cout<<*p++<<endl; //a
cout<<(void *)p<<endl; //0x7ffd011fe2e1
cout<<*(p++)<<endl; //b
cout<<(void *)p<<endl; //0x7ffd011fe2e2
cout<<(*p)++<<endl; //c
cout<<(void *)p<<endl; //0x7ffd011fe2e2
函数前加static
当函数前加了static,表示该函数失去了全局可见性,只在该函数所在的文件作用域内可见。编译器在该目标编译单元内只含有该函数的入口地址,没有函数名,其它编译单元便不能通过该函数名来调用该函数。
关于数组名与指针的区别的讨论
数组名不是指针,虽然它能够通过赋值操作符复制给指针,数组名是一种数据结构,不支持自加自减运算。
指针:你可以把它看做一个整型(但不是)数据类型,它是一个变量,存储得是一个地址,比如你可以把一个地址赋值给它,这时指针变量就存储这个地址,再把另一个地址赋值给它, 这时指针变量就存另一个地址,它是可以变的,所以可以作为左值。
数组:你声明一个数组,系统就分配了一个内存单元,这时数组名表示得是这个数组首元素得地址,这个地址是不能变得,可以把它看做(但不是)常量,所以不能作为左值 。
输出指针的地址:cout << (void *)ptr << endl;
常量指针的深入
已知,常量指针本身的地址值,但地址值指向的具体值不可变。换个说法就是:如果一个值是常量指针,无法通过该指针来改变其指向的值的大小。
如下,有一个char数组,将其数组名赋值给了常量指针p,则无法用 *p='x'的形式来对p所指向的值进行修改。常量指针存储的地址值可以改变,如代码中的0x7fff58a3a363和0x7fff58a3a364,但是,无一例外,都不能通过这些地址改变所指的值。如果想要改变常量指针所指向的数据,则要用其他办法,如代码中的 *cp='s',直接通过数组名来改变。
char cp[] = "qwer";
const char * p = cp;
cout<<(void*)p<<endl; //0x7fff58a3a363
cout<<*p<<endl; //q
*cp='s';
cout<<(void*)p<<endl; //0x7fff58a3a363
cout<<*p<<endl; //s
p++;
cout<<(void*)p<<endl; //0x7fff58a3a364
cout<<*p<<endl; //w
常量指针相比于其他指针,无法通过解引用符 " ' "来直接改变数据的值,这种特性常让常量指针作为函数的参数列表的形参,以防止函数中对指针的误操作修改了指针指向的内容。
此外,如果要将常量指针赋值给其他指针,其他指针也要声明为常量指针。
标准输出stdout,标准错误stderr
stdout, stdin, stderr的中文名字分别是标准输出,标准输入和标准错误。
stdout是行缓冲的,他的输出会放在一个buffer里面,只有到换行的时候,才会输出到屏幕。而stderr是无缓冲的,会直接输出
将其与fprintf结合,可直接输出当前语句所在的文件和行号:
fprintf(stderr, "%s:%d\n", __FILE__, __LINE__);
malloc
void *malloc(size_t size)
用于分配内存,返回动态分配内存块的首字节地址,如果请求失败,则返回NULL。
malloc可以与强制类型转换符结合,来指明返回的指针的指向,如:
(int *)malloc(n * sizeof(int));
memcpy
memcpy函数是C/C++语言中的一个用于内存复制的函数,声明在 string.h 中(C++是 cstring)。其原型是:
void *memcpy(void *destin, void *source, unsigned n);
作用是:以source指向的地址为起点,将连续的n个字节数据,复制到以destin指向的地址为起点的内存中。函数有三个参数,第一个是目标地址,第二个是源地址,第三个是数据长度。
使用memcpy函数时,需要注意:
- 数据长度(第三个参数)的单位是字节(1byte = 8bit)。
- 注意该函数有一个返回值,类型是void*,是一个指向destin的指针。
使用memcpy函数时,特别要注意数据长度。如果复制的数据类型是char,那么数据长度就等于元素的个数。而如果数据类型是其他(如int, double, 自定义结构体等),就要特别注意数据长度的值。好的习惯是,无论拷贝何种数据类型,都用 n * sizeof(type_name)
的写法。
valgrind
函数重载的条件
- 函数名称必须相同。
- 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
- 函数的返回类型可以相同也可以不相同。
- 仅仅返回类型不同不足以成为函数的重载。
字符串中的 ' \0 ' 表示空字符(null)
char text[] 字符串数组若没有 '\0',只能当做数组处理,若有 '\0',可以看做字符串,可以cou<<text,否则不可以。
用字符串初始化字符数组时,"\0"附带在后面与前面的字符一起作为字符数组的元素。在内存中,就是根据"\0"来确认字符串,如果找不到就会沿着字符一直找下去。它占用内存空间,但是不计入串长。用字符串初始化字符数组时,系统会在字符数组的末尾自动加上一个字符"\0",因此数组的大小比字符串中实际字符的个数大
函数体外只能定义全局变量和对象,不能执行语句或调用函数
int n;
n = 1;
int main(){
return 0;
}
上述代码在第二行的时候会出现错误:此声明没有存储类或类型说明符。
解决办法就是去掉函数体外的赋值(n=1),改到函数体内部。原因则如题所示,函数体外不能执行赋值语句。
int n;
int main(){
n = 1;
return 0;
}
迭代器和反向迭代器
c.begin() 返回一个迭代器,它指向容器c的第一个元素
c.end() 返回一个迭代器,它指向容器c的最后一个元素的下一个位置
c.rbegin() 返回一个逆序迭代器,它指向容器c的最后一个元素
c.rend() 返回一个逆序迭代器,它指向容器c的第一个元素前面的位置
计算幂次方的函数
int res=pow(2,5);//计算2的5次方
小技巧——返回数中的第n个数字
例如对于数10345,要返回其中第2个数字‘0’:
int num = 10345;
string str=to_string(num);
return str[1]-'0';
argc与argv[]
通常我们在写主函数时都是void main()或int main() {..return 0;},但ANSI-C(美国国家标准协会,C的第一个标准ANSI发布)在C89/C99中main()函数主要形式为:
(1).int main(void)
(2).int main(int argc,char *argv[]) = int main(int argc,char **argv).
其参数argc和argv用于运行时,把命令行参数传入主程序.其中ARG是指arguments,即参数.具体含义如下:
(1).int argc:英文名为arguments count(参数计数)
count of cmd line args,运行程序传送给main函数的命令行参数总个数,包括可执行程序名,其中当argc=1时表示只有一个程序名称,此时存储在argv[0]中.
(2).char **argv:英文名为arguments value/vector(参数值)
pointer to table of cmd line args,字符串数组,用来存放指向字符串参数的指针数组,每个元素指向一个参数,空格分隔参数,其长度为argc.数组下标从0开始,argv[argc]=NULL.
argv[0] 指向程序运行时的全路径名
argv[1] 指向程序在DOS命令中执行程序名后的第一个字符串
argv[2] 指向执行程序名后的第二个字符串
argv[argc] 为NULL.
stderr,strerror,errno 辨析
- stderr是C、C++中fprint函数的标准输出流,类似的还有stdin,stdout,其作用是将标准错误输出到屏幕
- errno是一个在linux C 发生异常时,系统自动赋值的全局变量,在头文件<errno.h>中,不同的值可以表示不同的意思,程序员可以通过查找对应出错误
- strerror函数可直接返回错误,免去了查找的过程,然后可以配合fprint函数中的stderr将错误信息输出 char *strerror(int errno)
一般将三者结合使用 fprintf( stderr, "%s\n" , strerror(int errno) ) 将错误信息打印到屏幕上。
define与inline比较
define:定义预编译时处理的宏,只是简单的字符串替换,无类型检查。
inline:
- inline关键字用来定义一个类的内联函数,引入它的主要原因是用它替代C中表达式形式的宏定义,编译阶段完成。
- 内联函数要做类型安全检查,inline是指嵌入代码,在调用函数的地方不是跳转,而是把代码直接写到那里去,对于短小的函数来说,inline函数可以得到一定效率的提升,和c时代的宏函数相比,inline函数更加安全可靠,这个是以增加空间的消耗为代价的。
函数后面加const
函数后面加上const,通常用于类的成员函数中。在编译文件时,编译器会自动给类中每一个成员函数加一个this指针。
在一个类的成员函数后面加上const后,就表明这个函数是只读函数,不能改变类的成员变量。实际上,也就是对这个this指针加上了const修饰,使其变为指针常量。
但是也有方法来修改,有两种途径可以修改:一是将变量前边加mutable修饰,另一个是用static_cast将常量指针this转换为普通指针,通过这个指针修改。
给一个成员函数后面加上const,可以实现该函数的重载,调用时,根据对象的const性选择重载函数。
#include<funtional>中的function
function是c++11引入的新的类型,它是一个模版类,用来表示可调用对象(函数,函数指针,lamada表达式,bind创建的对象,重载了函数调用运算符的类
sizeof关于字符串指针
char c[5] = "asdf"; //会出错
char c[5] = "asdf";
cout << c << endl;
cout << sizeof(c) << endl << strlen(c) << endl; // 输出 5 4