C++ 学习 零碎笔记

目录

在循环中return和break的区别

 i++与++i

与或非

各个数据类型的最大最小值

递归

类 构造函数 冒号 初始化列表

类 对象的初始化 new  野指针

for循环的第三个参数

string与int型数据之间的转换 

char与int型数据的转换 

多个string相加

常量指针与指针常量

静态常量

char string 比较

深拷贝(待补充)

容器迭代器加减法的注意点

关于指针的赋值运算符

 pass by value 与 pass by reference to const

 关于char类型的一些思考

关于类对象用成员函数访问其他对象private数据的问题 

在std内使用模板的注意点:

向上取整ceil()   向下取整floor()

If(a=b)的含义

c++学习之new int()和new int[]的区别

 typedef和define区别

int型数据与long型数据

补充:关于int型数据上下界的问题

空指针与野指针

realloc与野指针

单独大括号的意义

const 迭代器与const_iterator区别

类内的静态成员变量和静态成员函数

for循环的三个表达式执行的顺序 

const加引用 

#if #else ...的作用

指针占用内存大小:

 小李飞刀:位运算

do while(0)的应用

unsigned_int 的最大值

enum关键字 

断言(assert() )的应用

#if 0 ... #endif

union与struct

指针的算数运算

函数前加static

关于数组名与指针的区别的讨论 

输出指针的地址:cout << (void *)ptr << endl;

常量指针的深入

标准输出stdout,标准错误stderr

 malloc

memcpy

valgrind 

函数重载的条件

字符串中的 ' \0 ' 表示空字符(null)  

函数体外只能定义全局变量和对象,不能执行语句或调用函数

迭代器和反向迭代器

计算幂次方的函数

小技巧——返回数中的第n个数字

argc与argv[]

stderr,strerror,errno 辨析 

 define与inline比较

函数后面加const

#include中的function

sizeof关于字符串指针


在循环中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位。

类型存储字节表示范围
int4-2147483648~2147483647
short int2-32768~+32767
long4-2147483648~2147483647(二十亿,约1 0 10 10^{10}1010)
long long89223372036854775808~+9223372036854775807(九百亿亿,约1 0 19 10^{19}1019)
__int6489223372036854775808~+9223372036854775807

补充:关于int型数据上下界的问题

int型数据在内存中占用4个字节,即用32位的比特位来表示,对于正数和负数,需要用到一个符号位来区别,正数的符号位是0,负数的符号位是1。那么如果是4字节的数据,从0x0000~0x7fff表示的是0以及正数1 ~ 2^{31}-1(0111 1111 1111 1111 = 2^{31}-1) 从0x8000~0xffff用来表示负数,在此范围内,一共有2^{31}个数,(因为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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值